diff --git a/.gitignore b/.gitignore index 813da845..09a47dab 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ venv/* dist/ .DS_Store + +build/ \ No newline at end of file diff --git a/unit/__init__.py b/unit/__init__.py index 8076f8c6..371846ce 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -1,9 +1,15 @@ from unit.api.application_resource import ApplicationResource +from unit.api.check_deposit_resource import CheckDepositResource +from unit.api.batch_release_resource import BatchReleaseResource +from unit.api.check_payment_resource import CheckPaymentResource +from unit.api.check_stop_payment_resource import CheckStopPaymentResource from unit.api.customer_resource import CustomerResource from unit.api.account_resource import AccountResource from unit.api.card_resource import CardResource from unit.api.transaction_resource import TransactionResource from unit.api.payment_resource import PaymentResource +from unit.api.repayment_resource import RepaymentResource +from unit.api.ach_resource import AchResource from unit.api.statement_resource import StatementResource from unit.api.customerToken_resource import CustomerTokenResource from unit.api.counterparty_resource import CounterpartyResource @@ -19,6 +25,10 @@ from unit.api.authorization_resource import AuthorizationResource from unit.api.authorization_request_resource import AuthorizationRequestResource from unit.api.account_end_of_day_resource import AccountEndOfDayResource +from unit.api.reward_resource import RewardResource +from unit.api.dispute_resource import DisputeResource +from unit.api.received_payment_resource import ReceivedPaymentResource +from unit.api.check_registered_address import CheckRegisteredAddressResource __all__ = ["api", "models", "utils"] @@ -31,6 +41,9 @@ def __init__(self, api_url, token): self.cards = CardResource(api_url, token) self.transactions = TransactionResource(api_url, token) self.payments = PaymentResource(api_url, token) + self.check_deposits = CheckDepositResource(api_url, token) + self.repayments = RepaymentResource(api_url, token) + self.ach = AchResource(api_url, token) self.statements = StatementResource(api_url, token) self.customerTokens = CustomerTokenResource(api_url, token) self.counterparty = CounterpartyResource(api_url, token) @@ -46,3 +59,10 @@ def __init__(self, api_url, token): self.authorizations = AuthorizationResource(api_url, token) self.authorization_requests = AuthorizationRequestResource(api_url, token) self.account_end_of_day = AccountEndOfDayResource(api_url, token) + self.rewards = RewardResource(api_url, token) + self.batchRelease = BatchReleaseResource(api_url, token) + self.check_payments = CheckPaymentResource(api_url, token) + self.check_stop_payments = CheckStopPaymentResource(api_url, token) + self.disputes = DisputeResource(api_url, token) + self.received_payments = ReceivedPaymentResource(api_url, token) + self.check_registered_address = CheckRegisteredAddressResource(api_url, token) diff --git a/unit/api/account_resource.py b/unit/api/account_resource.py index 06fb60bd..26c35056 100644 --- a/unit/api/account_resource.py +++ b/unit/api/account_resource.py @@ -7,7 +7,7 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "accounts" - def create(self, request: CreateDepositAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: + def create(self, request: CreateAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: payload = request.to_json_api() response = super().post(self.resource, payload) if super().is_20x(response.status_code): @@ -33,6 +33,23 @@ def reopen_account(self, account_id: str, reason: str = "ByCustomer") -> Union[U else: return UnitError.from_json_api(response.json()) + def freeze_account(self, request: FreezeAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.account_id}/freeze", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AccountDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def unfreeze_account(self, account_id: str) -> Union[UnitResponse[AccountDTO], UnitError]: + response = super().post(f"{self.resource}/{account_id}/unfreeze") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AccountDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + def get(self, account_id: str, include: Optional[str] = "") -> Union[UnitResponse[AccountDTO], UnitError]: response = super().get(f"{self.resource}/{account_id}", {"include": include}) if super().is_20x(response.status_code): @@ -52,7 +69,7 @@ def list(self, params: ListAccountParams = None) -> Union[UnitResponse[List[Acco else: return UnitError.from_json_api(response.json()) - def update(self, request: PatchDepositAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: + def update(self, request: PatchAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: payload = request.to_json_api() response = super().patch(f"{self.resource}/{request.account_id}", payload) if super().is_20x(response.status_code): diff --git a/unit/api/ach_resource.py b/unit/api/ach_resource.py new file mode 100644 index 00000000..f19835db --- /dev/null +++ b/unit/api/ach_resource.py @@ -0,0 +1,34 @@ +from unit.api.base_resource import BaseResource +from unit.models.payment import * +from unit.models.codecs import DtoDecoder, split_json_api_single_response + + +class AchResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "ach" + + def simulate_transmit(self, request: SimulateTransmitAchRequest) -> Union[UnitResponse[PaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"sandbox/{self.resource}/transmit", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + # TODO Fix dto + _id, _type, attributes, relationships = split_json_api_single_response(data) + print("simulate_transmit") + print("data", data) + print("_id, _type, attributes, relationships", _id, _type, attributes, relationships) + return UnitResponse[SimulateAchPaymentDTO](SimulateAchPaymentDTO.from_json_api(_id, _type, attributes, relationships), None) + else: + return UnitError.from_json_api(response.json()) + + def simulate_clear(self, request: SimulateClearAchRequest) -> Union[UnitResponse[PaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"sandbox/{self.resource}/clear", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + # TODO Fix dto + _id, _type, attributes, relationships = split_json_api_single_response(data) + return UnitResponse[SimulateAchPaymentDTO](SimulateAchPaymentDTO.from_json_api(_id, _type, attributes, relationships), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/applicationForm_resource.py b/unit/api/applicationForm_resource.py index 3ef83f3e..0aa12c4a 100644 --- a/unit/api/applicationForm_resource.py +++ b/unit/api/applicationForm_resource.py @@ -10,7 +10,10 @@ def __init__(self, api_url, token): def create(self, request: CreateApplicationFormRequest) -> Union[UnitResponse[ApplicationFormDTO], UnitError]: payload = request.to_json_api() - response = super().post(self.resource, payload) + response = super().post( + self.resource, + payload + ) if super().is_20x(response.status_code): data = response.json().get("data") return UnitResponse[ApplicationFormDTO](DtoDecoder.decode(data), None) diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 7156c7d3..82d041e7 100644 --- a/unit/api/application_resource.py +++ b/unit/api/application_resource.py @@ -71,3 +71,20 @@ def update(self, request: PatchApplicationRequest) -> Union[UnitResponse[Applica else: return UnitError.from_json_api(response.json()) + def approve_sb(self, request: ApproveApplicationSBRequest): + url = f"sandbox/{self.resource}/{request.application_id}/approve" + + payload = request.to_json_api() + response = super().post(url, payload) + + if response.ok: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse(data, included) + # TODO need DTOs for this response + # if data["type"] == "individualApplication": + # return UnitResponse[IndividualApplicationDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + # else: + # return UnitResponse[BusinessApplicationDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/authorization_request_resource.py b/unit/api/authorization_request_resource.py index 56815ee8..dc8dca7b 100644 --- a/unit/api/authorization_request_resource.py +++ b/unit/api/authorization_request_resource.py @@ -44,3 +44,11 @@ def decline(self, request: DeclineAuthorizationRequest) -> Union[UnitResponse[Pu else: return UnitError.from_json_api(response.json()) + def sandbox_simulate(self, request: SimulateAuthorizationRequest) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"sandbox/{self.resource}/purchase", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[PurchaseAuthorizationRequestDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/authorization_resource.py b/unit/api/authorization_resource.py index 28a3fec7..3991bce1 100644 --- a/unit/api/authorization_resource.py +++ b/unit/api/authorization_resource.py @@ -25,4 +25,4 @@ def list(self, params: ListAuthorizationParams = None) -> Union[UnitResponse[Lis data = response.json().get("data") return UnitResponse[AuthorizationDTO](DtoDecoder.decode(data), None) else: - return UnitError.from_json_api(response.json()) + return UnitError.from_json_api(response.json()) \ No newline at end of file diff --git a/unit/api/base_resource.py b/unit/api/base_resource.py index b8b06547..2cab48ef 100644 --- a/unit/api/base_resource.py +++ b/unit/api/base_resource.py @@ -14,22 +14,22 @@ def __init__(self, api_url, token): "user-agent": "unit-python-sdk" } - def get(self, resource: str, params: Dict = None, headers: Optional[Dict[str, str]] = None): - return requests.get(f"{self.api_url}/{resource}", params=params, headers=self.__merge_headers(headers)) + def get(self, resource: str, params: Dict = None, headers: Optional[Dict[str, str]] = None, timeout: float = None): + return requests.get(f"{self.api_url}/{resource}", params=params, headers=self.__merge_headers(headers), timeout=timeout) - def post(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None): + def post(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None, timeout: float = None): data = json.dumps(data, cls=UnitEncoder) if data is not None else None - return requests.post(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers)) + return requests.post(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers), timeout=timeout) - def patch(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None): + def patch(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None, timeout: float = None): data = json.dumps(data, cls=UnitEncoder) if data is not None else None - return requests.patch(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers)) + return requests.patch(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers), timeout=timeout) - def delete(self, resource: str, params: Dict = None, headers: Optional[Dict[str, str]] = None): - return requests.delete(f"{self.api_url}/{resource}", params=params, headers=self.__merge_headers(headers)) + def delete(self, resource: str, params: Dict = None, headers: Optional[Dict[str, str]] = None, timeout: float = None): + return requests.delete(f"{self.api_url}/{resource}", params=params, headers=self.__merge_headers(headers), timeout=timeout) - def put(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None): - return requests.put(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers)) + def put(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None, timeout: float = None): + return requests.put(f"{self.api_url}/{resource}", data=data, headers=self.__merge_headers(headers), timeout=timeout) def __merge_headers(self, headers: Optional[Dict[str, str]] = None): if not headers: diff --git a/unit/api/batch_release_resource.py b/unit/api/batch_release_resource.py new file mode 100644 index 00000000..e8928741 --- /dev/null +++ b/unit/api/batch_release_resource.py @@ -0,0 +1,17 @@ +from unit.api.base_resource import BaseResource +from unit.models.batch_release import * +from unit.models.codecs import DtoDecoder + + +class BatchReleaseResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = 'batch-releases' + + def create(self, batch_releases: List[BatchReleaseDTO]) -> Union[UnitResponse[List[BatchReleaseDTO]], UnitError]: + response = super().post(self.resource, {"data": batch_releases}) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[List[BatchReleaseDTO]](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/check_deposit_resource.py b/unit/api/check_deposit_resource.py new file mode 100644 index 00000000..53f8e779 --- /dev/null +++ b/unit/api/check_deposit_resource.py @@ -0,0 +1,31 @@ +from unit.api.base_resource import BaseResource +from unit.models.check_deposit import * +from unit.models.codecs import DtoDecoder +from unit.models.transaction import * + + +class CheckDepositResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "check-deposits" + + def get(self, check_deposit_id: str) -> Union[UnitResponse[CheckDepositDTO], UnitError]: + params = {} + response = super().get(f"{self.resource}/{check_deposit_id}", params) + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[TransactionDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListTransactionParams = None) -> Union[UnitResponse[List[CheckDepositDTO]], UnitError]: + raise NotImplementedError() + + def get_image(self, check_deposit_id: str, is_back_side: Optional[bool] = False) -> Union[UnitResponse[bytes], UnitError]: + params = {} + response = super().get(f"{self.resource}/{check_deposit_id}/{'back' if is_back_side else 'front'}", params) + if response.status_code == 200: + return UnitResponse[bytes](response.content, None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py new file mode 100644 index 00000000..cff6a1ea --- /dev/null +++ b/unit/api/check_payment_resource.py @@ -0,0 +1,54 @@ +from unit.api.base_resource import BaseResource +from unit.models.authorization_request import * +from unit.models.check_payment import ApproveCheckPaymentRequest, CheckPaymentDTO, ReturnCheckPaymentRequest +from unit.models.codecs import DtoDecoder +from unit.models.payment import CreateCheckPaymentRequest + + +class CheckPaymentResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "check-payments" + + def get(self, check_payment_id: str) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: + response = super().get(f"{self.resource}/{check_payment_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[check_payment_id](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def approve(self, request: ApproveCheckPaymentRequest) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.check_payment_id}/approve", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CheckPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def create(self, request: CreateCheckPaymentRequest, timeout: float = None) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}", payload, timeout=timeout) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CheckPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def cancel(self, check_payment_id: str) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: + response = super().post(f"{self.resource}/{check_payment_id}/cancel") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CheckPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def return_check(self, request: ReturnCheckPaymentRequest) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.check_payment_id}/return", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CheckPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/check_registered_address.py b/unit/api/check_registered_address.py new file mode 100644 index 00000000..b6ae299e --- /dev/null +++ b/unit/api/check_registered_address.py @@ -0,0 +1,21 @@ +from unit.api.base_resource import BaseResource +from unit.models import * +from unit.models.check_registered_address import CheckRegisteredAddressRequest, CheckRegisteredAddressResponse +from unit.models.codecs import DtoDecoder + + +class CheckRegisteredAddressResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "applications/check-registered-agent-address" + + def create(self, request: CheckRegisteredAddressRequest) -> Union[UnitResponse[CheckRegisteredAddressResponse], UnitError]: + payload = request.to_json_api() + response = super().post(self.resource, payload) + + if response.ok: + data = response.json().get("data") + return UnitResponse[CheckRegisteredAddressResponse](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/unit/api/check_stop_payment_resource.py b/unit/api/check_stop_payment_resource.py new file mode 100644 index 00000000..203fb7ef --- /dev/null +++ b/unit/api/check_stop_payment_resource.py @@ -0,0 +1,20 @@ +from unit.api.base_resource import BaseResource +from unit.models.authorization_request import * +from unit.models.check_stop_payment import CheckStopPaymentDTO +from unit.models.codecs import DtoDecoder +from unit.models.payment import CreateCheckStopPaymentRequest + + +class CheckStopPaymentResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "stop-payments" + + def create(self, request: CreateCheckStopPaymentRequest) -> Union[UnitResponse[CheckStopPaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CheckStopPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/customer_resource.py b/unit/api/customer_resource.py index d164452c..bbcbfc05 100644 --- a/unit/api/customer_resource.py +++ b/unit/api/customer_resource.py @@ -38,3 +38,12 @@ def list(self, params: ListCustomerParams = None) -> Union[UnitResponse[List[Cus return UnitResponse[CustomerDTO](DtoDecoder.decode(data), None) else: return UnitError.from_json_api(response.json()) + + def archive(self, request: ArchiveCustomerRequest) -> Union[UnitResponse[CustomerDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.customer_id}/archive", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CustomerDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/dispute_resource.py b/unit/api/dispute_resource.py new file mode 100644 index 00000000..04d7124b --- /dev/null +++ b/unit/api/dispute_resource.py @@ -0,0 +1,18 @@ +from unit.api.base_resource import BaseResource +from unit.models.codecs import DtoDecoder +from unit.models.dispute import * + + + +class DisputeResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "disputes" + + def get(self, dispute_id: str) -> Union[UnitResponse[DisputeDTO], UnitError]: + response = super().get(f"{self.resource}/{dispute_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[DisputeDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/payment_resource.py b/unit/api/payment_resource.py index 6ef30a43..241ce540 100644 --- a/unit/api/payment_resource.py +++ b/unit/api/payment_resource.py @@ -1,6 +1,6 @@ from unit.api.base_resource import BaseResource from unit.models.payment import * -from unit.models.codecs import DtoDecoder +from unit.models.codecs import DtoDecoder, split_json_api_single_response class PaymentResource(BaseResource): @@ -8,9 +8,9 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "payments" - def create(self, request: CreatePaymentRequest) -> Union[UnitResponse[PaymentDTO], UnitError]: + def create(self, request: CreatePaymentRequest, timeout: float = None) -> Union[UnitResponse[PaymentDTO], UnitError]: payload = request.to_json_api() - response = super().post(self.resource, payload) + response = super().post(self.resource, payload, timeout=timeout) if super().is_20x(response.status_code): data = response.json().get("data") return UnitResponse[PaymentDTO](DtoDecoder.decode(data), None) @@ -45,3 +45,21 @@ def list(self, params: ListPaymentParams = None) -> Union[UnitResponse[List[Paym else: return UnitError.from_json_api(response.json()) + def simulate_incoming_ach(self, request: SimulateIncomingAchRequest) -> Union[UnitResponse[PaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"sandbox/{self.resource}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + # TODO Fix dto + _id, _type, attributes, relationships = split_json_api_single_response(data) + return UnitResponse[SimulateIncomingAchPaymentDTO](SimulateIncomingAchPaymentDTO.from_json_api(_id, _type, attributes, relationships), None) + else: + return UnitError.from_json_api(response.json()) + + def cancel(self, payment_id: str) -> Union[UnitResponse[PaymentDTO], UnitError]: + response = super().post(f"{self.resource}/{payment_id}/cancel") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[PaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/received_payment_resource.py b/unit/api/received_payment_resource.py index b453d6ac..888fd891 100644 --- a/unit/api/received_payment_resource.py +++ b/unit/api/received_payment_resource.py @@ -38,6 +38,14 @@ def list(self, params: ListReceivedPaymentParams = None) -> Union[UnitResponse[L def advance(self, payment_id: str) -> Union[UnitResponse[AchReceivedPaymentDTO], UnitError]: response = super().post(f"{self.resource}/{payment_id}/advance") + if response.status_code == 200: + data = response.json().get("data") + return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def reprocess(self, payment_id: str) -> Union[UnitResponse[AchReceivedPaymentDTO], UnitError]: + response = super().post(f"{self.resource}/{payment_id}/reprocess") if response.status_code == 200: data = response.json().get("data") return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), None) diff --git a/unit/api/repayment_resource.py b/unit/api/repayment_resource.py new file mode 100644 index 00000000..74276816 --- /dev/null +++ b/unit/api/repayment_resource.py @@ -0,0 +1,46 @@ +from typing import Union, List, Optional + +from unit.api.base_resource import BaseResource +from unit.models import UnitResponse, UnitError +from unit.models.codecs import DtoDecoder +from unit.models.repayment import ( + RepaymentDTO, + CreateRepaymentRequest, + ListRepaymentParams, +) + + +class RepaymentResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "repayments" + + def create( + self, request: CreateRepaymentRequest + ) -> Union[UnitResponse[RepaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().post(self.resource, payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[RepaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, repayment_id: str) -> Union[UnitResponse[RepaymentDTO], UnitError]: + response = super().get(f"{self.resource}/{repayment_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[RepaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list( + self, params: Optional[ListRepaymentParams] = None + ) -> Union[UnitResponse[List[RepaymentDTO]], UnitError]: + params = params or ListRepaymentParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[List[RepaymentDTO]](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/reward_resource.py b/unit/api/reward_resource.py new file mode 100644 index 00000000..8cafa463 --- /dev/null +++ b/unit/api/reward_resource.py @@ -0,0 +1,39 @@ +from typing import Union, List + +from unit.api.base_resource import BaseResource +from unit.models import UnitResponse, UnitError +from unit.models.reward import RewardDTO, ListRewardsParams, CreateRewardRequest + +from unit.models.codecs import DtoDecoder + + +class RewardResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "rewards" + + def create(self, request: CreateRewardRequest) -> Union[UnitResponse[RewardDTO], UnitError]: + payload = request.to_json_api() + response = super().post(self.resource, payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[RewardDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, reward_id: str) -> Union[UnitResponse[RewardDTO], UnitError]: + response = super().get(f"{self.resource}/{reward_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[RewardDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListRewardsParams = None) -> Union[UnitResponse[List[RewardDTO]], UnitError]: + params = params or ListRewardsParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[List[RewardDTO]](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/api/statement_resource.py b/unit/api/statement_resource.py index 8d0f47b1..791acb7f 100644 --- a/unit/api/statement_resource.py +++ b/unit/api/statement_resource.py @@ -15,7 +15,7 @@ def get(self, params: GetStatementParams) -> Union[UnitResponse[str], UnitError] response = super().get(f"{self.resource}/{params.statement_id}/{params.output_type}", parameters) if response.status_code == 200: - return UnitResponse[str](response.text, None) + return UnitResponse[bytes](response.content, None) else: return UnitError.from_json_api(response.json()) @@ -23,7 +23,7 @@ def get_bank_verification(self, account_id: str, include_proof_of_funds: Optiona response = super().get(f"{self.resource}/{account_id}/bank/pdf", {"includeProofOfFunds": include_proof_of_funds}) if response.status_code == 200: - return UnitResponse[str](response.text, None) + return UnitResponse[bytes](response.content, None) else: return UnitError.from_json_api(response.json()) diff --git a/unit/api/transaction_resource.py b/unit/api/transaction_resource.py index 2ab9d369..d9291033 100644 --- a/unit/api/transaction_resource.py +++ b/unit/api/transaction_resource.py @@ -9,8 +9,9 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "transactions" - def get(self, transaction_id: str, include: Optional[str] = "") -> Union[UnitResponse[TransactionDTO], UnitError]: - response = super().get(f"{self.resource}/{transaction_id}", {"include": include}) + def get(self, transaction_id: str, account_id: str, include: Optional[str] = "") -> Union[UnitResponse[TransactionDTO], UnitError]: + params = {"filter[accountId]": account_id, "include": include} + response = super().get(f"{self.resource}/{transaction_id}", params) if response.status_code == 200: data = response.json().get("data") included = response.json().get("included") @@ -38,3 +39,20 @@ def update(self, request: PatchTransactionRequest) -> Union[UnitResponse[Transac else: return UnitError.from_json_api(response.json()) + def sandbox_simulate_purchase_transaction(self, request: SimulatePurchaseTransaction) -> Union[UnitResponse[PurchaseTransactionDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"sandbox/purchases", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[PurchaseTransactionDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def sandbox_simulate_card_transaction(self, request: SimulateCardTransaction) -> Union[UnitResponse[CardTransactionDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"sandbox/card-transactions", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CardTransactionDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index e7fe6e81..88e69d0f 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -3,6 +3,33 @@ from datetime import datetime, date +def to_camel_case(snake_str): + components = snake_str.lstrip("_").split("_") + # We capitalize the first letter of each component except the first one + # with the 'title' method and join them together. + return components[0] + "".join(x.title() for x in components[1:]) + + +def extract_attributes(list_of_attributes, attributes): + extracted_attributes = {} + for a in list_of_attributes: + if a in attributes: + extracted_attributes[a] = attributes[a] + + return extracted_attributes + + +class UnitDTO(object): + def to_dict(self): + if type(self) is dict: + return self + else: + v = vars(self) + return dict( + (to_camel_case(k), val) for k, val in v.items() if val is not None + ) + + class Relationship(object): def __init__(self, _type: str, _id: str): self.type = _type @@ -12,7 +39,8 @@ def to_dict(self): return {"type": self.type, "id": self.id} -T = TypeVar('T') +T = TypeVar("T") + class RelationshipArray(Generic[T]): def __init__(self, l: List[T]): @@ -33,10 +61,41 @@ class UnitRequest(object): def to_json_api(self) -> Dict: pass + def vars_to_attributes_dict(self, ignore: List[str] = []) -> Dict: + attributes = {} + + for k in self.__dict__: + if k != "relationships" and k not in ignore: + v = getattr(self, k) + if v: + attributes[to_camel_case(k)] = v + + return attributes + + def to_payload( + self, + _type: str, + relationships: Dict[str, Relationship] = None, + ignore: List[str] = [], + ) -> Dict: + payload = { + "data": { + "type": _type, + "attributes": self.vars_to_attributes_dict(ignore), + } + } + + if relationships: + payload["data"]["relationships"] = relationships + + return payload + + class UnitParams(object): def to_dict(self) -> Dict: pass + class RawUnitObject(object): def __init__(self, _id, _type, attributes, relationships): self.id = _id @@ -44,9 +103,16 @@ def __init__(self, _id, _type, attributes, relationships): self.attributes = attributes self.relationships = relationships + class UnitErrorPayload(object): - def __init__(self, title: str, status: str, detail: Optional[str] = None, details: Optional[str] = None, - source: Optional[Dict] = None): + def __init__( + self, + title: str, + status: str, + detail: Optional[str] = None, + details: Optional[str] = None, + source: Optional[Dict] = None, + ): self.title = title self.status = status self.detail = detail @@ -66,35 +132,77 @@ def from_json_api(data: Dict): errors = [] for err in data["errors"]: errors.append( - UnitErrorPayload(err.get("title"), err.get("status"), err.get("detail", None), - err.get("details", None), err.get("source", None)) + UnitErrorPayload( + err.get("title"), + err.get("status"), + err.get("detail", None), + err.get("details", None), + err.get("source", None), + ) ) return UnitError(errors) def __str__(self): - return json.dumps({"errors": [{"title": err.title, "status": err.status, "detail": err.detail, - "details": err.details, "source": err.source} for err in self.errors]}) - - + return json.dumps( + { + "errors": [ + { + "title": err.title, + "status": err.status, + "detail": err.detail, + "details": err.details, + "source": err.source, + } + for err in self.errors + ] + } + ) + + +Occupation = Literal["ArchitectOrEngineer", "BusinessAnalystAccountantOrFinancialAdvisor", + "CommunityAndSocialServicesWorker", "ConstructionMechanicOrMaintenanceWorker", "Doctor", + "Educator", "EntertainmentSportsArtsOrMedia", "ExecutiveOrManager", "FarmerFishermanForester", + "FoodServiceWorker", "GigWorker", "HospitalityOfficeOrAdministrativeSupportWorker", + "HouseholdManager", "JanitorHousekeeperLandscaper", "Lawyer", "ManufacturingOrProductionWorker", + "MilitaryOrPublicSafety", "NurseHealthcareTechnicianOrHealthcareSupport", + "PersonalCareOrServiceWorker", "PilotDriverOperator", "SalesRepresentativeBrokerAgent", + "ScientistOrTechnologist", "Student"] +AnnualIncome = Literal["UpTo10k", "Between10kAnd25k", "Between25kAnd50k", "Between50kAnd100k", "Between100kAnd250k", + "Over250k"] +SourceOfIncome = Literal["EmploymentOrPayrollIncome", "PartTimeOrContractorIncome", "InheritancesAndGifts", + "PersonalInvestments", "BusinessOwnershipInterests", "GovernmentBenefits"] Status = Literal["Approved", "Denied", "PendingReview"] -Title = Literal["CEO", "COO", "CFO", "President"] -EntityType = Literal["Corporation", "LLC", "Partnership"] +Title = Literal["CEO", "COO", "CFO", "President", "BenefitsAdministrationOfficer", "CIO", "VP", "AVP", "Treasurer", + "Secretary", "Controller", "Manager", "Partner", "Member"] +EntityType = Literal["Corporation", "LLC", "Partnership", "PubliclyTradedCorporation", "PrivatelyHeldCorporation", + "NotForProfitOrganization"] + -class FullName(object): +class FullName(UnitDTO): def __init__(self, first: str, last: str): self.first = first self.last = last + def __str__(self): + return f"{self.first} {self.last}" + @staticmethod def from_json_api(data: Dict): return FullName(data.get("first"), data.get("last")) # todo: Alex - use typing.Literal for multi accepted values (e.g country) -class Address(object): - def __init__(self, street: str, city: str, state: str, postal_code: str, country: str, - street2: Optional[str] = None): +class Address(UnitDTO): + def __init__( + self, + street: str, + city: str, + state: str, + postal_code: str, + country: str, + street2: Optional[str] = None, + ): self.street = street self.street2 = street2 self.city = city @@ -104,11 +212,17 @@ def __init__(self, street: str, city: str, state: str, postal_code: str, country @staticmethod def from_json_api(data: Dict): - return Address(data.get("street"), data.get("city"), data.get("state"), - data.get("postalCode"), data.get("country"), data.get("street2", None)) + return Address( + data.get("street"), + data.get("city"), + data.get("state"), + data.get("postalCode"), + data.get("country"), + data.get("street2", None), + ) -class Phone(object): +class Phone(UnitDTO): def __init__(self, country_code: str, number: str): self.country_code = country_code self.number = number @@ -118,7 +232,7 @@ def from_json_api(data: Dict): return Phone(data.get("countryCode"), data.get("number")) -class BusinessContact(object): +class BusinessContact(UnitDTO): def __init__(self, full_name: FullName, email: str, phone: Phone): self.full_name = full_name self.email = email @@ -126,13 +240,19 @@ def __init__(self, full_name: FullName, email: str, phone: Phone): @staticmethod def from_json_api(data: Dict): - return BusinessContact(FullName.from_json_api(data.get("fullName")), data.get("email"), Phone.from_json_api(data.get("phone"))) + return BusinessContact( + FullName.from_json_api(data.get("fullName")), + data.get("email"), + Phone.from_json_api(data.get("phone")), + ) -class Officer(object): +class Officer(UnitDTO): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, status: Optional[Status] = None, title: Optional[Title] = None, ssn: Optional[str] = None, - passport: Optional[str] = None, nationality: Optional[str] = None): + passport: Optional[str] = None, nationality: Optional[str] = None, id_theft_score: Optional[str] = None, + occupation: Optional[Occupation] = None, annual_income: Optional[AnnualIncome] = None, + source_of_income: Optional[SourceOfIncome] = None): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -143,18 +263,36 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.ssn = ssn self.passport = passport self.nationality = nationality + self.id_theft_score = id_theft_score + self.occupation = occupation + self.annual_income = annual_income + self.source_of_income = source_of_income @staticmethod def from_json_api(data: Dict): return Officer(data.get("fullName"), data.get("dateOfBirth"), data.get("address"), data.get("phone"), - data.get("email"), data.get("status"), data.get("title"), data.get("ssn"), data.get("passport"), - data.get("nationality")) - - -class BeneficialOwner(object): - def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: Optional[Status] = None, ssn: Optional[str] = None, passport: Optional[str] = None, - nationality: Optional[str] = None, percentage: Optional[int] = None): + data.get("email"), data.get("status"), data.get("title"), data.get("ssn"), data.get("passport"), + data.get("nationality"), data.get("idTheftScore"), data.get("occupation"), data.get("annualIncome"), + data.get("sourceOfIncome")) + + +class BeneficialOwner(UnitDTO): + def __init__( + self, + full_name: FullName, + date_of_birth: date, + address: Address, + phone: Phone, + email: str, + status: Optional[Status] = None, + ssn: Optional[str] = None, + passport: Optional[str] = None, + nationality: Optional[str] = None, + percentage: Optional[int] = None, + occupation: Optional[Occupation] = None, + annual_income: Optional[AnnualIncome] = None, + source_of_income: Optional[SourceOfIncome] = None + ): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -165,32 +303,57 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.passport = passport self.nationality = nationality self.percentage = percentage + self.occupation = occupation + self.annual_income = annual_income + self.source_of_income = source_of_income @staticmethod def from_json_api(l: List): beneficial_owners = [] for data in l: - beneficial_owners.append(BeneficialOwner(data.get("fullName"), data.get("dateOfBirth"), data.get("address"), - data.get("phone"), data.get("email"), data.get("status"), data.get("ssn"), - data.get("passport"), data.get("nationality"), data.get("percentage"))) + beneficial_owners.append( + BeneficialOwner( + data.get("fullName"), + data.get("dateOfBirth"), + data.get("address"), + data.get("phone"), + data.get("email"), + data.get("status"), + data.get("ssn"), + data.get("passport"), + data.get("nationality"), + data.get("percentage"), + data.get("occupation"), + data.get("annualIncome"), + data.get("sourceOfIncome") + ) + ) return beneficial_owners -class AuthorizedUser(object): - def __init__(self, full_name: FullName, email: str, phone: Phone): +class AuthorizedUser(UnitDTO): + def __init__(self, full_name: FullName, email: str, phone: Phone, jwt_subject: Optional[str] = None): self.full_name = full_name self.email = email self.phone = phone + self.jwt_subject = jwt_subject @staticmethod - def from_json_api(l: List): + def from_json_api(l: List) -> List: authorized_users = [] for data in l: - authorized_users.append(AuthorizedUser(data.get("fullName"), data.get("email"), data.get("phone"))) + authorized_users.append( + AuthorizedUser( + data.get("fullName"), data.get("email"), data.get("phone"), data.get("jwtSubject") + ) + ) return authorized_users -class WireCounterparty(object): - def __init__(self, routing_number: str, account_number: str, name: str, address: Address): + +class WireCounterparty(UnitDTO): + def __init__( + self, routing_number: str, account_number: str, name: str, address: Address + ): self.routing_number = routing_number self.account_number = account_number self.name = name @@ -198,11 +361,18 @@ def __init__(self, routing_number: str, account_number: str, name: str, address: @staticmethod def from_json_api(data: Dict): - return WireCounterparty(data["routingNumber"], data["accountNumber"], data["name"], - Address.from_json_api(data["address"])) - -class Counterparty(object): - def __init__(self, routing_number: str, account_number: str, account_type: str, name: str): + return WireCounterparty( + data["routingNumber"], + data["accountNumber"], + data["name"], + Address.from_json_api(data["address"]), + ) + + +class Counterparty(UnitDTO): + def __init__( + self, routing_number: str, account_number: str, account_type: str, name: str + ): self.routing_number = routing_number self.account_number = account_number self.account_type = account_type @@ -210,31 +380,87 @@ def __init__(self, routing_number: str, account_number: str, account_type: str, @staticmethod def from_json_api(data: Dict): - return Counterparty(data["routingNumber"], data["accountNumber"], data["accountType"], data["name"]) + return Counterparty( + data["routingNumber"], + data["accountNumber"], + data["accountType"], + data["name"], + ) + + +class CheckCounterparty(UnitDTO): + def __init__( + self, routing_number: str, account_number: str, name: str + ): + self.routing_number = routing_number + self.account_number = account_number + self.name = name -class Coordinates(object): + @staticmethod + def from_json_api(data: Dict): + return CheckCounterparty( + data["routingNumber"], + data["accountNumber"], + data["name"], + ) + + +class CheckPaymentCounterparty(UnitDTO): + def __init__( + self, name: str, address: Address + ): + self.name = name + self.address = address + + @staticmethod + def from_json_api(data: Dict): + return CheckPaymentCounterparty( + data["name"], + data["address"], + ) + + +class Coordinates(UnitDTO): def __init__(self, longitude: int, latitude: int): self.longitude = longitude self.latitude = latitude @staticmethod def from_json_api(data: Dict): - return Coordinates(data["longitude"], data["latitude"]) + if data: + return Coordinates(data["longitude"], data["latitude"]) + else: + return None -class Merchant(object): - def __init__(self, name: str, type: int, category: str, location: Optional[str]): +class Merchant(UnitDTO): + def __init__( + self, name: str, type: int, category: Optional[str] = None, location: Optional[str] = None, _id: Optional[str] = None, + ): self.name = name self.type = type self.category = category self.location = location + self.id = _id @staticmethod - def from_json_api(data: Dict): - return Merchant(data["name"], data["type"], data["category"], data.get("location")) - -class CardLevelLimits(object): - def __init__(self, daily_withdrawal: int, daily_purchase: int, monthly_withdrawal: int, monthly_purchase: int): + def from_json_api(data: Dict = None): + if data is None: + return None + + return Merchant( + data["name"], data["type"], data.get("category"), data.get("location"), data.get("id") + ) + + +class CardLevelLimits(UnitDTO): + def __init__( + self, + daily_withdrawal: int, + daily_purchase: int, + monthly_withdrawal: int, + monthly_purchase: int, + ): self.daily_withdrawal = daily_withdrawal self.daily_purchase = daily_purchase self.monthly_withdrawal = monthly_withdrawal @@ -242,10 +468,15 @@ def __init__(self, daily_withdrawal: int, daily_purchase: int, monthly_withdrawa @staticmethod def from_json_api(data: Dict): - return CardLevelLimits(data["dailyWithdrawal"], data["dailyPurchase"], data["monthlyWithdrawal"], - data["monthlyPurchase"]) + return CardLevelLimits( + data["dailyWithdrawal"], + data["dailyPurchase"], + data["monthlyWithdrawal"], + data["monthlyPurchase"], + ) + -class CardTotals(object): +class CardTotals(UnitDTO): def __init__(self, withdrawals: int, deposits: int, purchases: int): self.withdrawals = withdrawals self.deposits = deposits @@ -256,7 +487,7 @@ def from_json_api(data: Dict): return CardTotals(data["withdrawals"], data["deposits"], data["purchases"]) -class DeviceFingerprint(object): +class DeviceFingerprint(UnitDTO): def __init__(self, value: str, provider: str = "iovation"): self.value = value self.provider = provider @@ -270,3 +501,91 @@ def to_json_api(self): @classmethod def from_json_api(cls, data: Dict): return cls(value=data["value"], provider=data["provider"]) + + +class CurrencyConversion(UnitDTO): + def __init__(self, original_currency: str, amount_in_original_currency: int, fx_rate: Optional[str]): + self.original_currency = original_currency + self.amount_in_original_currency = amount_in_original_currency + self.fx_rate = fx_rate + + @staticmethod + def from_json_api(data: Dict): + if not data: + return None + + return CurrencyConversion(data["originalCurrency"], data["amountInOriginalCurrency"], data.get("fxRate")) + + +class RichMerchantDataFacilitator(object): + def __init__(self, name: str, _type: Optional[str], logo: Optional[str]): + self.name = name + self.type = _type + self.logo = logo + + @staticmethod + def from_json_api(data: Dict): + if not data: + return None + + arr = [] + for c in data: + arr.append(RichMerchantDataFacilitator(c["name"], c.get("type"), c.get("logo"))) + + return arr + + +class RichMerchantDataCategory(object): + def __init__(self, name: str, icon: str): + self.name = name + self.icon = icon + + @staticmethod + def from_json_api(data: Dict): + if not data: + return None + + arr = [] + for c in data: + arr.append(RichMerchantDataCategory(c["name"], c["icon"])) + + return arr + + +class RichMerchantDataAddress(object): + def __init__(self, city: str, state: str, country: str, street: Optional[str]): + self.city = city + self.state = state + self.country = country + self.street = street + + @staticmethod + def from_json_api(data: Dict): + if not data: + return None + + return RichMerchantDataAddress(data["city"], data["state"], data["country"], data.get("street")) + + +class RichMerchantData(UnitDTO): + def __init__(self, name: str, website: Optional[str], logo: Optional[str], phone: Optional[str], + categories: Optional[List[RichMerchantDataCategory]], address: Optional[RichMerchantDataAddress], + coordinates: Optional[Coordinates], facilitators: Optional[List[RichMerchantDataFacilitator]]): + self.name = name + self.website = website + self.logo = logo + self.phone = phone + self.categories = categories + self.address = address + self.coordinates = coordinates + self.facilitators = facilitators + + @staticmethod + def from_json_api(data: Dict): + if not data: + return None + + return RichMerchantData(data["name"], data.get("website"), data.get("logo"), data.get("phone"), + RichMerchantDataCategory.from_json_api(data.get("categories")), data.get("address"), + Coordinates.from_json_api(data.get("coordinates")), + RichMerchantDataFacilitator.from_json_api(data.get("facilitators"))) diff --git a/unit/models/account.py b/unit/models/account.py index 9768faf9..41bf2558 100644 --- a/unit/models/account.py +++ b/unit/models/account.py @@ -2,9 +2,14 @@ from unit.models import * -AccountStatus = Literal["Open", "Closed"] +AccountStatus = Literal["Open", "Frozen", "Closed"] CloseReason = Literal["ByCustomer", "Fraud"] +FraudReason = Literal["ACHActivity", "CardActivity", "CheckActivity", "ApplicationHistory", "AccountActivity", + "ClientIdentified", "IdentityTheft", "LinkedToFraudulentCustomer"] +CreditAccountType = "creditAccount" +DepositAccountType = "depositAccount" +AccountTypes = Literal[CreditAccountType, DepositAccountType] class DepositAccountDTO(object): def __init__(self, id: str, created_at: datetime, name: str, deposit_product: str, routing_number: str, @@ -29,7 +34,33 @@ def from_json_api(_id, _type, attributes, relationships): ) -AccountDTO = Union[DepositAccountDTO] +class CreditAccountDTO(object): + def __init__(self, _id: str, created_at: datetime, updated_at: Optional[datetime], name: str, credit_terms: str, + currency: str, credit_limit: int, balance: int, hold: int, available: int, + tags: Optional[Dict[str, str]], status: AccountStatus, freeze_reason: Optional[str], + close_reason: Optional[str], close_reason_text: Optional[str], fraud_reason: Optional[FraudReason], + relationships: Optional[Dict[str, Relationship]]): + self.id = _id + self.type = CreditAccountType + self.attributes = {"createdAt": created_at, "updatedAt": updated_at, "name": name, "status": status, + "creditTerms": credit_terms, "currency": currency, "creditLimit": credit_limit, + "balance": balance, "hold": hold, "available": available, "tags": tags, + "freezeReason": freeze_reason, "closeReason": close_reason, + "closeReasonText": close_reason_text, "fraudReason": fraud_reason} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CreditAccountDTO(_id, date_utils.to_datetime(attributes["createdAt"]), + date_utils.to_datetime(attributes.get("updatedAt")), attributes["name"], + attributes["creditTerms"], attributes["currency"], attributes["creditLimit"], + attributes["balance"], attributes["hold"], attributes["available"], + attributes.get("tags"), attributes["status"], attributes.get("freezeReason"), + attributes.get("closeReason"), attributes.get("closeReasonText"), + attributes.get("fraudReason"), relationships) + + +AccountDTO = Union[DepositAccountDTO, CreditAccountDTO] class CreateDepositAccountRequest(UnitRequest): @@ -62,6 +93,39 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) +class CreateCreditAccountRequest(UnitRequest): + def __init__(self, credit_terms: str, credit_limit: int, relationships: Dict[str, Relationship], + tags: Optional[Dict[str, str]] = None, idempotency_key: Optional[str] = None): + self.credit_terms = credit_terms + self.credit_limit = credit_limit + self.tags = tags + self.idempotency_key = idempotency_key + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "creditAccount", + "attributes": { + "creditTerms": self.credit_terms, + "creditLimit": self.credit_limit + }, + "relationships": self.relationships + } + } + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) + +CreateAccountRequest = Union[CreateDepositAccountRequest, CreateCreditAccountRequest] class PatchDepositAccountRequest(UnitRequest): def __init__(self, account_id: str, deposit_product: Optional[str] = None, tags: Optional[Dict[str, str]] = None): @@ -88,6 +152,33 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) +class PatchCreditAccountRequest(UnitRequest): + def __init__(self, account_id: str, tags: Optional[Dict[str, str]] = None, credit_limit: Optional[int] = None): + self.account_id = account_id + self.tags = tags + self.credit_limit = credit_limit + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": CreditAccountType, + "attributes": {} + } + } + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + if self.credit_limit: + payload["data"]["attributes"]["creditLimit"] = self.credit_limit + + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) + +PatchAccountRequest = Union[PatchDepositAccountRequest, PatchCreditAccountRequest] + class AchTotals(object): def __init__(self, debits: int, credits: int): @@ -197,15 +288,20 @@ def from_json_api(attributes): CheckDepositAccountLimits.from_json_api(attributes["checkDeposit"])) +AccountCloseType = Literal["depositAccountClose", "creditAccountClose"] + + class CloseAccountRequest(UnitRequest): - def __init__(self, account_id: str, reason: Optional[Literal["ByCustomer", "Fraud"]] = "ByCustomer"): + def __init__(self, account_id: str, reason: Optional[Literal["ByCustomer", "Fraud"]] = "ByCustomer", + _type: AccountCloseType = "depositAccountClose"): self.account_id = account_id self.reason = reason + self._type = _type def to_json_api(self) -> Dict: payload = { "data": { - "type": "accountClose", + "type": self._type, "attributes": { "reason": self.reason, } @@ -215,24 +311,74 @@ def to_json_api(self) -> Dict: return payload def __repr__(self): - json.dumps(self.to_json_api()) + return json.dumps(self.to_json_api()) + + + +AccountFreezeReasons = Literal["Other", "Fraud"] +AccountFreezeTypes = Literal["accountFreeze", "creditAccountFreeze"] + + +class FreezeAccountRequest(UnitRequest): + def __init__( + self, + account_id: str, + reason: AccountFreezeReasons, + reason_text: Optional[str], + type: Optional[str] = "accountFreeze", + ): + self.account_id = account_id + self.reason = reason + self.reason_text = reason_text + self._type = type + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self._type, + "attributes": { + "reason": self.reason, + "reasonText": self.reason_text, + } + } + } + + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) class ListAccountParams(UnitParams): def __init__(self, offset: int = 0, limit: int = 100, customer_id: Optional[str] = None, - tags: Optional[object] = None, include: Optional[str] = None): + tags: Optional[Dict[str, str]] = None, include: Optional[str] = None, + status: Optional[AccountStatus] = None, from_balance: Optional[int] = None, + to_balance: Optional[int] = None, _type: Optional[AccountTypes] = None): self.offset = offset self.limit = limit self.customer_id = customer_id self.tags = tags self.include = include + self.status = status + self.from_balance = from_balance + self.to_balance = to_balance + self._type = _type def to_dict(self) -> Dict: parameters = {"page[limit]": self.limit, "page[offset]": self.offset} if self.customer_id: parameters["filter[customerId]"] = self.customer_id if self.tags: - parameters["filter[tags]"] = self.tags + parameters["filter[tags]"] = json.dumps(self.tags) if self.include: parameters["include"] = self.include + if self.status: + for idx, status_filter in enumerate(self.status): + parameters[f"filter[status][{idx}]"] = status_filter + if self._type: + parameters[f"filter[type]"] = self._type + if self.from_balance: + parameters["filter[fromBalance]"] = self.from_balance + if self.to_balance: + parameters["filter[toBalance]"] = self.to_balance return parameters diff --git a/unit/models/application.py b/unit/models/application.py index d8edfec6..06516cf0 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -4,76 +4,307 @@ ApplicationStatus = Literal["Approved", "Denied", "Pending", "PendingReview"] -DocumentType = Literal["IdDocument", "Passport", "AddressVerification", "CertificateOfIncorporation", - "EmployerIdentificationNumberConfirmation"] +DocumentType = Literal[ + "IdDocument", + "Passport", + "AddressVerification", + "CertificateOfIncorporation", + "EmployerIdentificationNumberConfirmation", + "SocialSecurityCard", + "ClientRequested", + "SelfieVerification", +] + +ApplicationDocumentStatus = Literal[ + "Required", + "ReceivedBack", + "ReceivedFront", + "Invalid", + "Approved", + "PendingReview" +] + +ReasonCode = Literal[ + "PoorQuality", + "NameMismatch", + "SSNMismatch", + "AddressMismatch", + "DOBMismatch", + "ExpiredId", + "EINMismatch", + "StateMismatch", + "Other", +] + +ApplicationTypes = Literal[ + "individualApplication", "businessApplication", "trustApplication" +] + + +Industry = Literal[ + "Retail", + "Wholesale", + "Restaurants", + "Hospitals", + "Construction", + "Insurance", + "Unions", + "RealEstate", + "FreelanceProfessional", + "OtherProfessionalServices", + "OnlineRetailer", + "OtherEducationServices", +] + +AnnualRevenue = Literal[ + "UpTo250k", + "Between250kAnd500k", + "Between500kAnd1m", + "Between1mAnd5m", + "Over5m", + "UpTo50k", + "Between50kAnd100k", + "Between100kAnd200k", + "Between200kAnd500k", + "Over500k", +] + +NumberOfEmployees = Literal[ + "One", + "Between2And5", + "Between5And10", + "Over10", + "UpTo10", + "Between10And50", + "Between50And100", + "Between100And500", + "Over500", +] + +CashFlow = Literal["Unpredictable", "Predictable"] + +BusinessVertical = Literal[ + "AdultEntertainmentDatingOrEscortServices", + "AgricultureForestryFishingOrHunting", + "ArtsEntertainmentAndRecreation", + "BusinessSupportOrBuildingServices", + "Cannabis", + "Construction", + "DirectMarketingOrTelemarketing", + "EducationalServices", + "FinancialServicesCryptocurrency", + "FinancialServicesDebitCollectionOrConsolidation", + "FinancialServicesMoneyServicesBusinessOrCurrencyExchange", + "FinancialServicesOther", + "FinancialServicesPaydayLending", + "GamingOrGambling", + "HealthCareAndSocialAssistance", + "HospitalityAccommodationOrFoodServices", + "LegalAccountingConsultingOrComputerProgramming", + "Manufacturing", + "Mining", + "Nutraceuticals", + "PersonalCareServices", + "PublicAdministration", + "RealEstate", + "ReligiousCivicAndSocialOrganizations", + "RepairAndMaintenance", + "RetailTrade", + "TechnologyMediaOrTelecom", + "TransportationOrWarehousing", + "Utilities", + "WholesaleTrade", +] + +Revocability = Literal["Revocable", "Irrevocable"] +SourceOfFunds = Literal[ + "Inheritance", "Salary", "Savings", "InvestmentReturns", "Gifts" +] -ReasonCode = Literal["PoorQuality", "NameMismatch", "SSNMismatch", "AddressMismatch", "DOBMismatch", "ExpiredId", - "EINMismatch", "StateMismatch", "Other"] - -ApplicationTypes = Literal["individualApplication", "businessApplication"] class IndividualApplicationDTO(object): - def __init__(self, id: str, created_at: datetime, full_name: FullName, address: Address, date_of_birth: date, - email: str, phone: Phone, status: ApplicationStatus, ssn: Optional[str], message: Optional[str], - ip: Optional[str], ein: Optional[str], dba: Optional[str], - sole_proprietorship: Optional[bool], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + def __init__( + self, + id: str, + created_at: datetime, + full_name: FullName, + address: Address, + date_of_birth: date, + email: str, + phone: Phone, + status: ApplicationStatus, + ssn: Optional[str], + message: Optional[str], + ip: Optional[str], + ein: Optional[str], + dba: Optional[str], + sole_proprietorship: Optional[bool], + business_vertical: Optional[BusinessVertical], + tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]], + ): self.id = id self.type = "individualApplication" - self.attributes = {"createdAt": created_at, "fullName": full_name, "address": address, - "dateOfBirth": date_of_birth, "email": email, "phone": phone, "status": status, "ssn": ssn, - "message": message, "ip": ip, "ein": ein, "dba": dba, - "soleProprietorship": sole_proprietorship, "tags": tags} + self.attributes = { + "createdAt": created_at, + "fullName": full_name, + "address": address, + "dateOfBirth": date_of_birth, + "email": email, + "phone": phone, + "status": status, + "ssn": ssn, + "message": message, + "ip": ip, + "ein": ein, + "dba": dba, + "soleProprietorship": sole_proprietorship, + "businessVertical": business_vertical, + "tags": tags, + } self.relationships = relationships @staticmethod def from_json_api(_id, _type, attributes, relationships): return IndividualApplicationDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), - FullName.from_json_api(attributes["fullName"]), Address.from_json_api(attributes["address"]), + _id, + date_utils.to_datetime(attributes["createdAt"]), + FullName.from_json_api(attributes["fullName"]), + Address.from_json_api(attributes["address"]), date_utils.to_date(attributes["dateOfBirth"]), - attributes["email"], Phone.from_json_api(attributes["phone"]), attributes["status"], - attributes.get("ssn"), attributes.get("message"), attributes.get("ip"), - attributes.get("ein"), attributes.get("dba"), attributes.get("soleProprietorship"), - attributes.get("tags"), relationships + attributes["email"], + Phone.from_json_api(attributes["phone"]), + attributes["status"], + attributes.get("ssn"), + attributes.get("message"), + attributes.get("ip"), + attributes.get("ein"), + attributes.get("dba"), + attributes.get("soleProprietorship"), + attributes.get("businessVertical"), + attributes.get("tags"), + relationships, ) class BusinessApplicationDTO(object): - def __init__(self, id: str, created_at: datetime, name: str, address: Address, phone: Phone, - status: ApplicationStatus, state_of_incorporation: str, entity_type: EntityType, - contact: BusinessContact, officer: Officer, beneficial_owners: [BeneficialOwner], ssn: Optional[str], - message: Optional[str], ip: Optional[str], ein: Optional[str], dba: Optional[str], - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + def __init__( + self, + id: str, + created_at: datetime, + name: str, + address: Address, + phone: Phone, + status: ApplicationStatus, + state_of_incorporation: str, + entity_type: EntityType, + contact: BusinessContact, + officer: Officer, + beneficial_owners: [BeneficialOwner], + ssn: Optional[str], + message: Optional[str], + ip: Optional[str], + ein: Optional[str], + dba: Optional[str], + website: Optional[str], + year_of_incorporation: Optional[str], + business_vertical: Optional[BusinessVertical], + annual_revenue: Optional[AnnualRevenue], + number_of_employees: Optional[NumberOfEmployees], + cash_flow: Optional[CashFlow], + countries_of_operation: Optional[List[str]], + operating_address: Optional[Address], + tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]], + ): self.id = id self.type = "businessApplication" - self.attributes = {"createdAt": created_at, "name": name, "address": address, "phone": phone, - "status": status, "ssn": ssn, "stateOfIncorporation": state_of_incorporation, "ssn": ssn, - "message": message, "ip": ip, "ein": ein, "entityType": entity_type, "dba": dba, - "contact": contact, "officer": officer, "beneficialOwners":beneficial_owners, "tags": tags} + self.attributes = { + "createdAt": created_at, + "name": name, + "address": address, + "phone": phone, + "status": status, + "ssn": ssn, + "stateOfIncorporation": state_of_incorporation, + "message": message, + "ip": ip, + "ein": ein, + "entityType": entity_type, + "dba": dba, + "website": website, + "yearOfIncorporation": year_of_incorporation, + "businessVertical": business_vertical, + "annualRevenue": annual_revenue, + "numberOfEmployees": number_of_employees, + "cashFlow": cash_flow, + "countriesOfOperation": countries_of_operation, + "contact": contact, + "officer": officer, + "beneficialOwners": beneficial_owners, + "operatingAddress": operating_address, + "tags": tags, + } self.relationships = relationships - @staticmethod def from_json_api(_id, _type, attributes, relationships): return BusinessApplicationDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("name"), - Address.from_json_api(attributes["address"]), Phone.from_json_api(attributes["phone"]), - attributes["status"], attributes.get("stateOfIncorporation"), attributes.get("entityType"), - BusinessContact.from_json_api(attributes["contact"]), Officer.from_json_api(attributes["officer"]), - BeneficialOwner.from_json_api(attributes["beneficialOwners"]), attributes.get("ssn"), - attributes.get("message"), attributes.get("ip"), attributes.get("ein"), attributes.get("dba"), - attributes.get("tags"), relationships + _id, + date_utils.to_datetime(attributes["createdAt"]), + attributes.get("name"), + Address.from_json_api(attributes["address"]), + Phone.from_json_api(attributes["phone"]), + attributes["status"], + attributes.get("stateOfIncorporation"), + attributes.get("entityType"), + BusinessContact.from_json_api(attributes["contact"]), + Officer.from_json_api(attributes["officer"]), + BeneficialOwner.from_json_api(attributes["beneficialOwners"]), + attributes.get("ssn"), + attributes.get("message"), + attributes.get("ip"), + attributes.get("ein"), + attributes.get("dba"), + attributes.get("website"), + attributes.get("year_of_incorporation"), + attributes.get("business_vertical"), + attributes.get("annual_revenue"), + attributes.get("number_of_employees"), + attributes.get("cash_flow"), + attributes.get("countries_of_operation"), + attributes.get("operating_address"), + attributes.get("tags"), + relationships, ) + ApplicationDTO = Union[IndividualApplicationDTO, BusinessApplicationDTO] + class CreateIndividualApplicationRequest(UnitRequest): - def __init__(self, full_name: FullName, date_of_birth: date, address: Address, email: str, phone: Phone, - ip: str = None, ein: str = None, dba: str = None, sole_proprietorship: bool = None, - passport: str = None, nationality: str = None, ssn = None, - device_fingerprints: Optional[List[DeviceFingerprint]] = None, idempotency_key: str = None, - tags: Optional[Dict[str, str]] = None): + def __init__( + self, + full_name: FullName, + date_of_birth: date, + address: Address, + email: str, + phone: Phone, + ip: str = None, + ein: str = None, + dba: str = None, + sole_proprietorship: bool = None, + passport: str = None, + nationality: str = None, + ssn=None, + device_fingerprints: Optional[List[DeviceFingerprint]] = None, + idempotency_key: str = None, + tags: Optional[Dict[str, str]] = None, + website: str = None, + annual_revenue: Optional[AnnualRevenue] = None, + number_of_employees: Optional[NumberOfEmployees] = None, + business_vertical: Optional[BusinessVertical] = None, + ): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -89,6 +320,10 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, e self.device_fingerprints = device_fingerprints self.idempotency_key = idempotency_key self.tags = tags + self.annual_revenue = annual_revenue + self.number_of_employees = number_of_employees + self.business_vertical = business_vertical + self.website = website def to_json_api(self) -> Dict: payload = { @@ -100,7 +335,7 @@ def to_json_api(self) -> Dict: "address": self.address, "email": self.email, "phone": self.phone, - } + }, } } @@ -114,7 +349,9 @@ def to_json_api(self) -> Dict: payload["data"]["attributes"]["dba"] = self.dba if self.sole_proprietorship: - payload["data"]["attributes"]["soleProprietorship"] = self.sole_proprietorship + payload["data"]["attributes"][ + "soleProprietorship" + ] = self.sole_proprietorship if self.ssn: payload["data"]["attributes"]["ssn"] = self.ssn @@ -129,11 +366,25 @@ def to_json_api(self) -> Dict: payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key if self.device_fingerprints: - payload["data"]["attributes"]["deviceFingerprints"] = [e.to_json_api() for e in self.device_fingerprints] + payload["data"]["attributes"]["deviceFingerprints"] = [ + e.to_json_api() for e in self.device_fingerprints + ] if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.website: + payload["data"]["attributes"]["website"] = self.website + + if self.annual_revenue: + payload["data"]["attributes"]["annualRevenue"] = self.annual_revenue + + if self.number_of_employees: + payload["data"]["attributes"]["numberOfEmployees"] = self.number_of_employees + + if self.business_vertical: + payload["data"]["attributes"]["businessVertical"] = self.business_vertical + return payload def __repr__(self): @@ -141,9 +392,33 @@ def __repr__(self): class CreateBusinessApplicationRequest(UnitRequest): - def __init__(self, name: str, address: Address, phone: Phone, state_of_incorporation: str, ein: str, - contact: BusinessContact, officer: Officer, beneficial_owners: [BeneficialOwner], - entity_type: EntityType, dba: str = None, ip: str = None, website: str = None): + def __init__( + self, + name: str, + address: Address, + phone: Phone, + state_of_incorporation: str, + ein: str, + contact: BusinessContact, + officer: Officer, + beneficial_owners: [BeneficialOwner], + entity_type: EntityType, + tags: Optional[Dict[str, str]] = None, + dba: str = None, + ip: str = None, + website: str = None, + industry: Optional[Industry] = None, + annual_revenue: Optional[AnnualRevenue] = None, + number_of_employees: Optional[NumberOfEmployees] = None, + cash_flow: Optional[CashFlow] = None, + year_of_incorporation: Optional[str] = None, + countries_of_operation: Optional[List[str]] = None, + stock_symbol: Optional[str] = None, + business_vertical: Optional[BusinessVertical] = None, + device_fingerprints: Optional[List[DeviceFingerprint]] = None, + operating_address: Optional[Address] = None, + idempotency_key: Optional[str] = None, + ): self.name = name self.address = address self.phone = phone @@ -156,8 +431,20 @@ def __init__(self, name: str, address: Address, phone: Phone, state_of_incorpora self.dba = dba self.ip = ip self.website = website + self.tags = tags + self.industry = industry + self.annual_revenue = annual_revenue + self.number_of_employees = number_of_employees + self.cash_flow = cash_flow + self.year_of_incorporation = year_of_incorporation + self.countries_of_operation = countries_of_operation + self.stock_symbol = stock_symbol + self.business_vertical = business_vertical + self.device_fingerprints = device_fingerprints + self.operating_address = operating_address + self.idempotency_key = idempotency_key - def to_json_api(self) -> Dict: + def to_json_api(self) -> dict: payload = { "data": { "type": "businessApplication", @@ -170,20 +457,49 @@ def to_json_api(self) -> Dict: "contact": self.contact, "officer": self.officer, "beneficialOwners": self.beneficial_owners, - "entityType": self.entity_type - } + "entityType": self.entity_type, + "yearOfIncorporation": self.year_of_incorporation, + "businessVertical": self.business_vertical, + }, } } if self.dba: payload["data"]["attributes"]["dba"] = self.dba + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + if self.ip: payload["data"]["attributes"]["ip"] = self.ip if self.website: payload["data"]["attributes"]["website"] = self.website + if self.industry: + payload["data"]["attributes"]["industry"] = self.industry + + if self.annual_revenue: + payload["data"]["attributes"]["annualRevenue"] = self.annual_revenue + + if self.number_of_employees: + payload["data"]["attributes"]["numberOfEmployees"] = self.number_of_employees + + if self.cash_flow: + payload["data"]["attributes"]["cashFlow"] = self.cash_flow + + if self.countries_of_operation: + payload["data"]["attributes"]["countriesOfOperation"] = self.countries_of_operation + + if self.stock_symbol: + payload["data"]["attributes"]["stockSymbol"] = self.stock_symbol + + if self.operating_address: + payload["data"]["attributes"]["operatingAddress"] = self.operating_address + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + return payload def __repr__(self): @@ -191,30 +507,69 @@ def __repr__(self): class ApplicationDocumentDTO(object): - def __init__(self, id: str, status: ApplicationStatus, document_type: DocumentType, description: str, name: str, - address: Optional[Address], date_of_birth: Optional[date], passport: Optional[str], ein: Optional[str], - reason_code: Optional[ReasonCode], reason: Optional[str]): + def __init__( + self, + id: str, + status: ApplicationDocumentStatus, + document_type: DocumentType, + description: str, + name: str, + address: Optional[Address], + date_of_birth: Optional[date], + passport: Optional[str], + ein: Optional[str], + reason_code: Optional[ReasonCode], + reason: Optional[str], + ): self.id = id self.type = "document" - self.attributes = {"status": status, "documentType": document_type, "description": description, "name": name, - "address": address, "dateOfBirth": date_of_birth, "passport": passport, "ein": ein, - "reasonCode": reason_code, "reason": reason} + self.attributes = { + "status": status, + "documentType": document_type, + "description": description, + "name": name, + "address": address, + "dateOfBirth": date_of_birth, + "passport": passport, + "ein": ein, + "reasonCode": reason_code, + "reason": reason, + } @staticmethod def from_json_api(_id, _type, attributes): - address = Address.from_json_api(attributes.get("address")) if attributes.get("address") else None + address = ( + Address.from_json_api(attributes.get("address")) + if attributes.get("address") + else None + ) return ApplicationDocumentDTO( - _id, attributes["status"], attributes["documentType"], attributes["description"], attributes["name"], - address, attributes.get("dateOfBirth"), attributes.get("passport"), - attributes.get("ein"), attributes.get("reasonCode"), attributes.get("reason") + _id, + attributes["status"], + attributes["documentType"], + attributes["description"], + attributes["name"], + address, + attributes.get("dateOfBirth"), + attributes.get("passport"), + attributes.get("ein"), + attributes.get("reasonCode"), + attributes.get("reason"), ) + FileType = Literal["jpeg", "png", "pdf"] class UploadDocumentRequest(object): - def __init__(self, application_id: str, document_id: str, file: IO, file_type: FileType, - is_back_side: Optional[bool] = False): + def __init__( + self, + application_id: str, + document_id: str, + file: IO, + file_type: FileType, + is_back_side: Optional[bool] = False, + ): self.application_id = application_id self.document_id = document_id self.file = file @@ -223,9 +578,15 @@ def __init__(self, application_id: str, document_id: str, file: IO, file_type: F class ListApplicationParams(UnitParams): - def __init__(self, offset: int = 0, limit: int = 100, email: Optional[str] = None, - tags: Optional[object] = None, query: Optional[str] = None, - sort: Optional[Literal["createdAt", "-createdAt"]] = None): + def __init__( + self, + offset: int = 0, + limit: int = 100, + email: Optional[str] = None, + tags: Optional[object] = None, + query: Optional[str] = None, + sort: Optional[Literal["createdAt", "-createdAt"]] = None, + ): self.offset = offset self.limit = limit self.email = email @@ -245,20 +606,20 @@ def to_dict(self) -> Dict: parameters["sort"] = self.sort return parameters + class PatchApplicationRequest(UnitRequest): - def __init__(self, application_id: str, type: ApplicationTypes = "individualApplication", - tags: Optional[Dict[str, str]] = None): + def __init__( + self, + application_id: str, + type: ApplicationTypes = "individualApplication", + tags: Optional[Dict[str, str]] = None, + ): self.application_id = application_id self.type = type self.tags = tags def to_json_api(self) -> Dict: - payload = { - "data": { - "type": self.type, - "attributes": {} - } - } + payload = {"data": {"type": self.type, "attributes": {}}} if self.tags: payload["data"]["attributes"]["tags"] = self.tags @@ -268,3 +629,16 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) + +class ApproveApplicationSBRequest(UnitRequest): + def __init__(self, application_id: str): + self.application_id = application_id + + def to_json_api(self) -> Dict: + payload = { + "data": {"type": "applicationApprove", "attributes": {"reason": "sandbox"}} + } + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) diff --git a/unit/models/authorization.py b/unit/models/authorization.py index a737cb2d..9f6de45b 100644 --- a/unit/models/authorization.py +++ b/unit/models/authorization.py @@ -7,23 +7,20 @@ class AuthorizationDTO(object): def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, status: AuthorizationStatus, - merchant_name: str, - merchant_type: int, merchant_category: str, merchant_location: Optional[str], recurring: bool, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + merchant: Merchant, recurring: bool, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): self.id = id self.type = "authorization" self.attributes = {"createdAt": created_at, "amount": amount, "cardLast4Digits": card_last_4_digits, - "status": status, "merchant": {"name": merchant_name, "type": merchant_type, - "category": merchant_category, "location": merchant_location}, + "status": status, "merchant": merchant, "recurring": recurring, "tags": tags} self.relationships = relationships @staticmethod def from_json_api(_id, _type, attributes, relationships): return AuthorizationDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["amount"], - attributes["cardLast4Digits"], attributes["status"], attributes["merchant"]["name"], - attributes["merchant"]["type"], attributes["merchant"]["category"], - attributes["merchant"].get("location"), attributes["recurring"], + attributes["cardLast4Digits"], attributes["status"], + Merchant.from_json_api(attributes["merchant"]), attributes["recurring"], attributes.get("tags"), relationships) diff --git a/unit/models/authorization_request.py b/unit/models/authorization_request.py index 6eed39a4..116047de 100644 --- a/unit/models/authorization_request.py +++ b/unit/models/authorization_request.py @@ -29,12 +29,12 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("partialApprovalAllowed"), attributes.get("approvedAmount"), attributes.get("declineReason"), attributes["merchant"]["name"], attributes["merchant"]["type"], - attributes["merchant"]["category"], + attributes["merchant"].get("category"), attributes["merchant"].get("location"), attributes["recurring"], attributes.get("tags"), relationships) -class ListPurchaseAuthorizationRequestParams(object): +class ListPurchaseAuthorizationRequestParams(UnitParams): def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, customer_id: Optional[str] = None): self.limit = limit @@ -51,7 +51,7 @@ def to_dict(self) -> Dict: return parameters -class ApproveAuthorizationRequest(object): +class ApproveAuthorizationRequest(UnitRequest): def __init__(self, authorization_id: str, amount: Optional[int] = None, tags: Optional[Dict[str, str]] = None): self.authorization_id = authorization_id self.amount = amount @@ -77,7 +77,7 @@ def __repr__(self): json.dumps(self.to_json_api()) -class DeclineAuthorizationRequest(object): +class DeclineAuthorizationRequest(UnitRequest): def __init__(self, authorization_id: str, reason: DeclineReason): self.authorization_id = authorization_id self.reason = reason @@ -96,3 +96,40 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) + + +class SimulateAuthorizationRequest(UnitRequest): + def __init__(self, amount: int, card_id: str, merchant_name: str, merchant_type: int, merchant_location: str): + self.amount = amount + self.card_id = card_id + self.merchant_name = merchant_name + self.merchant_type = merchant_type + self.merchant_location = merchant_location + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "purchaseAuthorizationRequest", + "attributes": { + "amount": self.amount, + "merchantName": self.merchant_name, + "merchantType": self.merchant_type, + "merchantLocation": self.merchant_location, + "recurring": False + }, + "relationships": { + "card": { + "data": { + "type": "card", + "id": self.card_id + } + } + } + + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) diff --git a/unit/models/batch_release.py b/unit/models/batch_release.py new file mode 100644 index 00000000..4e65af13 --- /dev/null +++ b/unit/models/batch_release.py @@ -0,0 +1,60 @@ +from unit.models import * + + +class BatchReleaseDTO(object): + def __init__(self, id: str, amount: int, description: str, sender_name: str, sender_address: Address, + sender_account_number: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "batchRelease" + self.attributes = { + "amount": amount, + "description": description, + "senderName": sender_name, + "senderAccountNumber": sender_account_number, + "senderAddress": sender_address, + "tags": tags, + } + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BatchReleaseDTO(_id, attributes["amount"], attributes["description"], attributes["senderName"], Address.from_json_api(attributes["senderAddress"]), attributes["senderAccountNumber"], attributes.get("tags"), relationships) + + +class CreateBatchRelease(UnitRequest): + def __init__(self, amount: int, description: str, sender_name: str, sender_address: Address, + sender_account_number: str, relationships: Optional[Dict[str, Relationship]], tags: Optional[Dict[str, str]] = None, + idempotency_key: Optional[str] = None): + self.amount = amount + self.description = description + self.sender_name = sender_name + self.sender_address = sender_address + self.sender_account_number = sender_account_number + self.tags = tags + self.idempotency_key = idempotency_key + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "type": "batchRelease", + "attributes": { + "amount": self.amount, + "description": self.description, + "senderName": self.sender_name, + "senderAccountNumber": self.sender_account_number, + "senderAddress": self.sender_address + }, + "relationships": self.relationships + } + + if self.idempotency_key: + payload["attributes"]["idempotencyKey"] = self.idempotency_key + + if self.tags: + payload["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) diff --git a/unit/models/benificial_owner.py b/unit/models/benificial_owner.py new file mode 100644 index 00000000..3b3fcd12 --- /dev/null +++ b/unit/models/benificial_owner.py @@ -0,0 +1,26 @@ +import json +from typing import Optional +from unit.models import * +from unit.utils import date_utils + + +class BenificialOwnerDTO(object): + def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, + status: Optional[Status] = None, ssn: Optional[str] = None, passport: Optional[str] = None, + nationality: Optional[str] = None, percentage: Optional[int] = None): + self.full_name = full_name + self.date_of_birth = date_of_birth + self.address = address + self.phone = phone + self.email = email + self.status = status + self.ssn = ssn + self.passport = passport + self.nationality = nationality + self.percentage = percentage + + @staticmethod + def from_json_api(attributes): + return BenificialOwnerDTO(attributes.get("fullName"), attributes.get("dateOfBirth"), attributes.get("address"), + attributes.get("phone"), attributes.get("email"), attributes.get("status"), attributes.get("ssn"), + attributes.get("passport"), attributes.get("nationality"), attributes.get("percentage")) diff --git a/unit/models/card.py b/unit/models/card.py index 5e7b7699..c8ed53f8 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -24,31 +24,55 @@ def from_json_api(_id, _type, attributes, relationships): ) -class BusinessDebitCardDTO(object): - def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration_date: str, ssn: str, - full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, passport: Optional[str], nationality: Optional[str], +class BusinessCardDTO(object): + def __init__(self, _id: str, _type: str, created_at: datetime, last_4_digits: str, expiration_date: str, + ssn: Optional[str], full_name: FullName, date_of_birth: date, address: Address, phone: Phone, + email: str, status: CardStatus, passport: Optional[str], nationality: Optional[str], shipping_address: Optional[Address], design: Optional[str], - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "businessDebitCard" + relationships: Optional[Dict[str, Relationship]], tags: Optional[Dict[str, str]]): + self.id = _id + self.type = _type self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, "ssn": ssn, "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, "phone": phone, "email": email, "status": status, "passport": passport, - "nationality": nationality, "shippingAddress": shipping_address, "design": design} + "nationality": nationality, "shippingAddress": shipping_address, "design": design, + "tags": tags} self.relationships = relationships + @staticmethod def from_json_api(_id, _type, attributes, relationships): - shipping_address = Address.from_json_api(attributes.get("shippingAddress")) if attributes.get("shippingAddress") else None - return BusinessDebitCardDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], - attributes["expirationDate"], attributes["ssn"], FullName.from_json_api(attributes["fullName"]), + return BusinessCardDTO( + _id, _type, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], + attributes["expirationDate"], attributes.get("ssn"), FullName.from_json_api(attributes["fullName"]), attributes["dateOfBirth"], Address.from_json_api(attributes["address"]), Phone.from_json_api(attributes["phone"]), attributes["email"], attributes["status"], attributes.get("passport"), attributes.get("nationality"), - shipping_address, attributes.get("design"), relationships + Address.from_json_api(attributes.get("shippingAddress")), attributes.get("design"), relationships, + attributes.get("tags") ) +class BusinessDebitCardDTO(BusinessCardDTO): + def __init__(self, card: BusinessCardDTO): + self.id = card.id + self.type = card.type + self.attributes = card.attributes + self.relationships = card.relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BusinessDebitCardDTO(BusinessCardDTO.from_json_api(_id, _type, attributes, relationships)) + + +class BusinessCreditCardDTO(BusinessCardDTO): + def __init__(self, card: BusinessCardDTO): + self.id = card.id + self.type = card.type + self.attributes = card.attributes + self.relationships = card.relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BusinessCreditCardDTO(BusinessCardDTO.from_json_api(_id, _type, attributes, relationships)) class IndividualVirtualDebitCardDTO(object): def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration_date: str, status: CardStatus, @@ -68,37 +92,71 @@ def from_json_api(_id, _type, attributes, relationships): class BusinessVirtualDebitCardDTO(object): - def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration_date: str, ssn: str, + def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration_date: str, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, passport: Optional[str], nationality: Optional[str], - relationships: Optional[Dict[str, Relationship]]): + status: CardStatus, passport: Optional[str] = None, nationality: Optional[str] = None, + relationships: Optional[Dict[str, Relationship]] = None): self.id = id self.type = "businessVirtualDebitCard" self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, - "ssn": ssn, "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, + "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, "phone": phone, "email": email, "status": status, "passport": passport, "nationality": nationality} - self.relationships = relationships + self.relationships = relationships or {} def from_json_api(_id, _type, attributes, relationships): return BusinessVirtualDebitCardDTO( _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], - attributes["expirationDate"], attributes["ssn"], FullName.from_json_api(attributes["fullName"]), + attributes["expirationDate"], FullName.from_json_api(attributes["fullName"]), attributes["dateOfBirth"], Address.from_json_api(attributes["address"]), Phone.from_json_api(attributes["phone"]), attributes["email"], attributes["status"], attributes.get("passport"), attributes.get("nationality"), relationships ) +class BusinessVirtualCardDTO(object): + def __init__(self, _id: str, _type: str, created_at: datetime, last_4_digits: str, expiration_date: str, + ssn: Optional[str], full_name: FullName, date_of_birth: date, address: Address, phone: Phone, + email: str, status: CardStatus, passport: Optional[str], nationality: Optional[str], + relationships: Optional[Dict[str, Relationship]], tags: Optional[Dict[str, str]]): + self.id = _id + self.type = _type + self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, + "ssn": ssn, "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, + "phone": phone, "email": email, "status": status, "passport": passport, + "nationality": nationality, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BusinessVirtualCardDTO( + _id, _type, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], + attributes["expirationDate"], attributes.get("ssn"), FullName.from_json_api(attributes["fullName"]), + attributes["dateOfBirth"], Address.from_json_api(attributes["address"]), + Phone.from_json_api(attributes["phone"]), attributes["email"], attributes["status"], + attributes.get("passport"), attributes.get("nationality"), relationships, attributes.get("tags")) -Card = Union[IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO, BusinessVirtualDebitCardDTO] +class BusinessVirtualCreditCardDTO(BusinessVirtualCardDTO): + def __init__(self, card: BusinessVirtualCardDTO): + self.id = card.id + self.type = card.type + self.attributes = card.attributes + self.relationships = card.relationships + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BusinessVirtualCreditCardDTO(BusinessVirtualCardDTO.from_json_api(_id, _type, attributes, relationships)) -class CreateIndividualDebitCard(object): - def __init__(self, relationships: Dict[str, Relationship], shipping_address: Optional[Address] = None, - design: Optional[str] = None, idempotency_key: Optional[str] = None, - tags: Optional[Dict[str, str]] = None): +Card = Union[IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO, BusinessVirtualDebitCardDTO, + BusinessVirtualCreditCardDTO, BusinessCreditCardDTO] + + +class CreateIndividualDebitCard(UnitRequest): + def __init__(self, relationships: Dict[str, Relationship], limits: Optional[CardLevelLimits] = None, + shipping_address: Optional[Address] = None, design: Optional[str] = None, + idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None): self.shipping_address = shipping_address self.design = design + self.limits = limits self.idempotency_key = idempotency_key self.tags = tags self.relationships = relationships @@ -115,6 +173,9 @@ def to_json_api(self) -> Dict: if self.shipping_address: payload["data"]["attributes"]["shippingAddress"] = self.shipping_address + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.design: payload["data"]["attributes"]["design"] = self.design @@ -129,17 +190,18 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessDebitCard(object): +class CreateBusinessCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, shipping_address: Optional[Address], ssn: Optional[str], passport: Optional[str], - nationality: Optional[str], design: Optional[str], idempotency_key: Optional[str], - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + relationships: Dict[str, Relationship], shipping_address: Optional[Address] = None, + ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, + design: Optional[str] = None, idempotency_key: Optional[str] = None, + tags: Optional[Dict[str, str]] = None, limits: Optional[CardLevelLimits] = None, + additional_embossed_text: Optional[str] = None, print_only_business_name: Optional[bool] = None): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address self.phone = phone self.email = email - self.status = status self.shipping_address = shipping_address self.ssn = ssn self.passport = passport @@ -148,11 +210,14 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.idempotency_key = idempotency_key self.tags = tags self.relationships = relationships + self.limits = limits + self.additional_embossed_text = additional_embossed_text + self.print_only_business_name = print_only_business_name - def to_json_api(self) -> Dict: + def to_json_api(self, _type: str) -> Dict: payload = { "data": { - "type": "businessDebitCard", + "type": _type, "attributes": { "fullName": self.full_name, "dateOfBirth": self.date_of_birth, @@ -185,15 +250,35 @@ def to_json_api(self) -> Dict: if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + + if self.additional_embossed_text: + payload["data"]["attributes"]["additionalEmbossedText"] = self.additional_embossed_text + + if self.print_only_business_name is not None: + payload["data"]["attributes"]["printOnlyBusinessName"] = self.print_only_business_name + return payload def __repr__(self): - json.dumps(self.to_json_api()) + return json.dumps(self.to_json_api()) + -class CreateIndividualVirtualDebitCard(object): +class CreateBusinessDebitCard(CreateBusinessCard): + def to_json_api(self): + return super().to_json_api("businessDebitCard") + + +class CreateBusinessCreditCard(CreateBusinessCard): + def to_json_api(self): + return super().to_json_api("businessCreditCard") + +class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, - tags: Optional[Dict[str, str]] = None): + limits: Optional[CardLevelLimits] = None, tags: Optional[Dict[str, str]] = None): self.idempotency_key = idempotency_key + self.limits = limits self.tags = tags self.relationships = relationships @@ -209,6 +294,9 @@ def to_json_api(self) -> Dict: if self.idempotency_key: payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.tags: payload["data"]["attributes"]["tags"] = self.tags @@ -218,28 +306,28 @@ def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessVirtualDebitCard(object): +class CreateBusinessVirtualCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, ssn: Optional[str], passport: Optional[str], nationality: Optional[str], - idempotency_key: Optional[str], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + relationships: Dict[str, Relationship], ssn: Optional[str] = None, passport: Optional[str] = None, + nationality: Optional[str] = None, idempotency_key: Optional[str] = None, + tags: Optional[Dict[str, str]] = None, limits: Optional[CardLevelLimits] = None): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address self.phone = phone self.email = email - self.status = status self.ssn = ssn self.passport = passport self.nationality = nationality self.idempotency_key = idempotency_key self.tags = tags self.relationships = relationships + self.limits = limits - def to_json_api(self) -> Dict: + def to_json_api(self, _type: str) -> Dict: payload = { "data": { - "type": "businessVirtualDebitCard", + "type": _type, "attributes": { "fullName": self.full_name, "dateOfBirth": self.date_of_birth, @@ -266,21 +354,35 @@ def to_json_api(self) -> Dict: if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + return payload def __repr__(self): - json.dumps(self.to_json_api()) + return json.dumps(self.to_json_api()) + + +class CreateBusinessVirtualDebitCard(CreateBusinessVirtualCard): + def to_json_api(self): + return super().to_json_api("businessVirtualDebitCard") + + +class CreateBusinessVirtualCreditCard(CreateBusinessVirtualCard): + def to_json_api(self): + return super().to_json_api("businessVirtualCreditCard") CreateCardRequest = Union[CreateIndividualDebitCard, CreateBusinessDebitCard, CreateIndividualVirtualDebitCard, - CreateBusinessVirtualDebitCard] + CreateBusinessVirtualDebitCard, CreateBusinessVirtualCreditCard, CreateBusinessCreditCard] -class PatchIndividualDebitCard(object): +class PatchIndividualDebitCard(UnitRequest): def __init__(self,card_id: str, shipping_address: Optional[Address] = None, design: Optional[str] = None, - tags: Optional[Dict[str, str]] = None): + limits: CardLevelLimits = None, tags: Optional[Dict[str, str]] = None): self.card_id = card_id self.shipping_address = shipping_address self.design = design + self.limits = limits self.tags = tags def to_json_api(self) -> Dict: @@ -294,6 +396,9 @@ def to_json_api(self) -> Dict: if self.shipping_address: payload["data"]["attributes"]["shippingAddress"] = self.shipping_address + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.design: payload["data"]["attributes"]["design"] = self.design @@ -306,19 +411,23 @@ def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessDebitCard(object): +class PatchBusinessCard(UnitRequest): def __init__(self, card_id: str, shipping_address: Optional[Address] = None, address: Optional[Address] = None, phone: Optional[Phone] = None, email: Optional[str] = None, design: Optional[str] = None, - tags: Optional[Dict[str, str]] = None): + tags: Optional[Dict[str, str]] = None, limits: Optional[CardLevelLimits] = None): self.card_id = card_id self.shipping_address = shipping_address + self.address = address + self.phone = phone + self.email = email self.design = design self.tags = tags + self.limits = limits - def to_json_api(self) -> Dict: + def to_json_api(self, _type: str = "businessDebitCard") -> Dict: payload = { "data": { - "type": "businessDebitCard", + "type": _type, "attributes": {}, } } @@ -341,14 +450,27 @@ def to_json_api(self) -> Dict: if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + return payload def __repr__(self): - json.dumps(self.to_json_api()) + return json.dumps(self.to_json_api()) -class PatchIndividualVirtualDebitCard(object): - def __init__(self, card_id: str, tags: Optional[Dict[str, str]] = None): + +class PatchBusinessDebitCard(PatchBusinessCard): + pass + + +class PatchBusinessCreditCard(PatchBusinessCard): + def to_json_api(self) -> Dict: + return super().to_json_api("businessCreditCard") + +class PatchIndividualVirtualDebitCard(UnitRequest): + def __init__(self, card_id: str, limits: CardLevelLimits = None, tags: Optional[Dict[str, str]] = None): self.card_id = card_id + self.limits = limits self.tags = tags def to_json_api(self) -> Dict: @@ -359,27 +481,31 @@ def to_json_api(self) -> Dict: } } - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + + payload["data"]["attributes"]["tags"] = self.tags or {} return payload def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessVirtualDebitCard(object): +class PatchBusinessVirtualCard(UnitRequest): def __init__(self, card_id: str, address: Optional[Address] = None, phone: Optional[Phone] = None, - email: Optional[str] = None, tags: Optional[Dict[str, str]] = None): + email: Optional[str] = None, tags: Optional[Dict[str, str]] = None, + _type: str = "businessVirtualDebitCard", limits: Optional[CardLevelLimits] = None): self.card_id = card_id self.address = address self.phone = phone self.email = email self.tags = tags + self.limits = limits - def to_json_api(self) -> Dict: + def to_json_api(self, _type: str = "businessVirtualDebitCard") -> Dict: payload = { "data": { - "type": "businessVirtualDebitCard", + "type": _type, "attributes": {}, } } @@ -396,13 +522,23 @@ def to_json_api(self) -> Dict: if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + return payload - def __repr__(self): - json.dumps(self.to_json_api()) + +class PatchBusinessVirtualDebitCard(PatchBusinessVirtualCard): + pass + + +class PatchBusinessVirtualCreditCard(PatchBusinessVirtualCard): + def to_json_api(self) -> Dict: + return super().to_json_api("businessVirtualCreditCard") + PatchCardRequest = Union[PatchIndividualDebitCard, PatchBusinessDebitCard, PatchIndividualVirtualDebitCard, - PatchBusinessVirtualDebitCard] + PatchBusinessVirtualDebitCard, PatchBusinessCreditCard, PatchBusinessVirtualCreditCard] class ReplaceCardRequest(object): def __init__(self, shipping_address: Optional[Address] = None): @@ -421,8 +557,8 @@ def to_json_api(self) -> Dict: return payload - def __repr__(self): - json.dumps(self.to_json_api()) + def __repr__(self): + json.dumps(self.to_json_api()) PinStatus = Literal["Set", "NotSet"] @@ -450,13 +586,17 @@ def from_json_api(attributes): class ListCardParams(UnitParams): def __init__(self, offset: int = 0, limit: int = 100, account_id: Optional[str] = None, - customer_id: Optional[str] = None, tags: Optional[object] = None, include: Optional[str] = None): + customer_id: Optional[str] = None, tags: Optional[Dict[str, str]] = None, include: Optional[str] = None, + sort: Optional[Literal["createdAt", "-createdAt"]] = None, + status: Optional[List[CardStatus]] = None): self.offset = offset self.limit = limit self.account_id = account_id self.customer_id = customer_id self.tags = tags self.include = include + self.sort = sort + self.status = status def to_dict(self) -> Dict: parameters = {"page[limit]": self.limit, "page[offset]": self.offset} @@ -465,8 +605,13 @@ def to_dict(self) -> Dict: if self.account_id: parameters["filter[accountId]"] = self.account_id if self.tags: - parameters["filter[tags]"] = self.tags + parameters["filter[tags]"] = json.dumps(self.tags) if self.include: parameters["include"] = self.include + if self.sort: + parameters["sort"] = self.sort + if self.status: + for idx, status_filter in enumerate(self.status): + parameters[f"filter[status][{idx}]"] = status_filter return parameters diff --git a/unit/models/check_deposit.py b/unit/models/check_deposit.py new file mode 100644 index 00000000..a240fe19 --- /dev/null +++ b/unit/models/check_deposit.py @@ -0,0 +1,63 @@ +import json +from typing import Optional +from unit.models import * +from unit.utils import date_utils + +CheckDepositStatus = Literal[ + "AwaitingImages", "AwaitingFrontImage", "AwaitingBackImage", "Pending", "PendingReview", "Rejected", "Clearing", "Sent", "Canceled", "Returned", +] + + +class CheckDepositDTO(object): + def __init__( + self, + id: str, + created_at: datetime, + status: CheckDepositStatus, + reason: Optional[str], + description: str, + amount: int, + check_number: str, + counterparty: Optional[CheckCounterparty], + settlement_date: Optional[datetime], + tags: Optional[Dict[str, str]], + relationships: Dict[str, Relationship] + ): + self.id = id + self.type = "authorization" + self.attributes = { + "createdAt": created_at, + "status": status, + "description": description, + "amount": amount, + "checkNumber": check_number, + } + if reason: + self.attributes["reason"] = reason + if counterparty: + self.attributes["counterparty"] = counterparty + if settlement_date: + self.attributes["settlementDate"] = settlement_date + if tags: + self.attributes["tages"] = tags + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + settlement_date = attributes.get("settlementDate") + if settlement_date: + settlement_date = date_utils.to_datetime(settlement_date) + + return CheckDepositDTO( + id=_id, + created_at=date_utils.to_datetime(attributes["createdAt"]), + status=attributes["status"], + reason=attributes.get("reason"), + description=attributes["description"], + amount=attributes["amount"], + check_number=attributes.get("checkNumber"), + counterparty=attributes.get("counterparty"), + settlementDate=settlement_date, + tags=attributes.get("tags"), + relationships=relationships, + ) diff --git a/unit/models/check_payment.py b/unit/models/check_payment.py new file mode 100644 index 00000000..87a16f87 --- /dev/null +++ b/unit/models/check_payment.py @@ -0,0 +1,162 @@ +import json +from typing import Optional, Literal +from unit.models import * +from unit.utils import date_utils + + +CheckPaymentStatus = Literal["New", "Pending", "PendingCancellation", "Canceled", "InDelivery", "Delivered", + "ReturnedToSender", "Processed", "PendingReview", "MarkedForReturn", "Returned", "Rejected"] +CheckPaymentAdditionalVerificationStatus = Literal["Required", "NotRequired", "Approved"] +CheckPaymentReturnStatusReason = Literal[ + "NotSufficientFunds", + "UncollectedFundsHold", + "StopPayment", + "ClosedAccount", + "UnableToLocateAccount", + "FrozenOrBlockedAccount", + "StaleDated", + "PostDated", + "NotValidCheckOrCashItem", + "AlteredOrFictitious", + "UnableToProcess", + "ItemExceedsDollarLimit", + "NotAuthorized", + "ReferToMaker", + "UnusableImage", + "DuplicatePresentment", + "WarrantyBreach", + "UnauthorizedWarrantyBreach" +] +CheckPaymentDeliveryStatus = Literal["Mailed", "InLocalArea", "Delivered", "Rerouted", "ReturnedToSender"] + + +class CheckPaymentDTO(object): + def __init__(self, id: str, created_at: datetime, updated_at: datetime, amount: int, status: CheckPaymentStatus, + description: str, originated: bool, check_number: Optional[str], on_us: Optional[str], + on_us_auxiliary: Optional[str], counterparty_routing_number: Optional[str], + return_status_reason: Optional[CheckPaymentReturnStatusReason], reject_reason: Optional[str], + pending_review_reasons: Optional[List[str]], return_cutoff_time: Optional[datetime], + additional_verification_status: Optional[CheckPaymentAdditionalVerificationStatus], + tags: Optional[Dict[str, str]], delivery_status: Optional[CheckPaymentDeliveryStatus], + tracked_at: Optional[datetime], postal_code: Optional[str], expiration_date: Optional[date], + expected_delivery: Optional[date], send_at: Optional[datetime], + counterparty_name: Optional[str], counterparty_moved: Optional[bool], + counterparty_street: Optional[str], + counterparty_street2: Optional[str], counterparty_city: Optional[str], + counterparty_state: Optional[str], + counterparty_postal_code: Optional[str], counterparty_country: Optional[str], memo: Optional[str], + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "checkPayment" + self.attributes = self.attributes = { + "createdAt": created_at, + "updatedAt": updated_at, + "amount": amount, + "status": status, + "description": description, + "checkNumber": check_number, + "originated": originated, + "onUs": on_us, + "onUsAuxiliary": on_us_auxiliary, + "counterpartyRoutingNumber": counterparty_routing_number, + "returnStatusReason": return_status_reason, + "rejectReason": reject_reason, + "pendingReviewReasons": pending_review_reasons, + "returnCutoffTime": return_cutoff_time, + "additionalVerificationStatus": additional_verification_status, + "tags": tags, + "deliveryStatus": delivery_status, + "trackedAt": tracked_at, + "postalCode": postal_code, + "expirationDate": expiration_date, + "expectedDelivery": expected_delivery, + "sendAt": send_at, + "counterparty": { + "name": counterparty_name, + "moved": counterparty_moved, + "address": { + "street": counterparty_street, + "street2": counterparty_street2, + "city": counterparty_city, + "state": counterparty_state, + "postalCode": counterparty_postal_code, + "country": counterparty_country, + } + }, + "memo": memo, + } + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentDTO( + id=_id, + created_at=date_utils.to_datetime(attributes["createdAt"]), + updated_at=date_utils.to_datetime(attributes["updatedAt"]), + amount=attributes["amount"], + status=attributes["status"], + description=attributes["description"], + check_number=attributes.get("checkNumber"), + originated=attributes["originated"], + on_us=attributes.get("onUs"), + on_us_auxiliary=attributes.get("onUsAuxiliary"), + counterparty_routing_number=attributes.get("counterpartyRoutingNumber"), + return_status_reason=attributes.get("returnStatusReason"), + reject_reason=attributes.get("rejectReason"), + pending_review_reasons=attributes.get("pendingReviewReasons"), + return_cutoff_time=attributes.get("returnCutoffTime"), + additional_verification_status=attributes.get("additionalVerificationStatus"), + tags=attributes.get("tags"), + delivery_status=attributes.get("deliveryStatus"), + tracked_at=attributes.get("trackedAt"), + postal_code=attributes.get("postalCode"), + expiration_date=attributes.get("expirationDate"), + expected_delivery=attributes.get("expectedDelivery"), + send_at=attributes.get("sendAt"), + counterparty_name=attributes.get("counterparty", {}).get("name"), + counterparty_moved=attributes.get("counterparty", {}).get("moved"), + counterparty_street=attributes.get("counterparty", {}).get("address", {}).get("street"), + counterparty_street2=attributes.get("counterparty", {}).get("address", {}).get("street2"), + counterparty_city=attributes.get("counterparty", {}).get("address", {}).get("city"), + counterparty_state=attributes.get("counterparty", {}).get("address", {}).get("state"), + counterparty_postal_code=attributes.get("counterparty", {}).get("address", {}).get("postalCode"), + counterparty_country=attributes.get("counterparty", {}).get("address", {}).get("country"), + memo=attributes.get("memo"), + relationships=relationships, + ) + + +class ApproveCheckPaymentRequest(UnitRequest): + def __init__(self, check_payment_id: str): + self.check_payment_id = check_payment_id + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "additionalVerification", + } + } + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class ReturnCheckPaymentRequest(UnitRequest): + def __init__(self, check_payment_id: str, reason: CheckPaymentReturnStatusReason): + self.check_payment_id = check_payment_id + self.reason = reason + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "checkPaymentReturn", + "attributes": { + "reason": self.reason, + } + } + } + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) diff --git a/unit/models/check_registered_address.py b/unit/models/check_registered_address.py new file mode 100644 index 00000000..6862a4c8 --- /dev/null +++ b/unit/models/check_registered_address.py @@ -0,0 +1,55 @@ +from unit.models import * + + + +class CheckRegisteredAddressRequest(UnitRequest): + def __init__( + self, + street: str, + city: str, + state: str, + postal_code: str, + country: str, + street2: Optional[str] = None, + ): + self.type = "checkRegisteredAgentAddress" + + self.attributes = { + "address": { + "street": street, + "city": city, + "state": state, + "postalCode": postal_code, + "country": country, + } + } + + if street2: + self.attributes["address"]["street2"] = street2 + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self.type, + "attributes": self.attributes, + } + } + return payload + + +class CheckRegisteredAddressResponse(UnitDTO): + def __init__( + self, + is_registered_agent_address: bool, + ): + self.type = "checkRegisteredAgentAddress" + self.is_registered_agent_address = is_registered_agent_address + self.attributes = { + "isRegisteredAgentAddress": is_registered_agent_address, + } + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckRegisteredAddressResponse( + attributes.get("isRegisteredAgentAddress"), + ) diff --git a/unit/models/check_stop_payment.py b/unit/models/check_stop_payment.py new file mode 100644 index 00000000..e5598ac4 --- /dev/null +++ b/unit/models/check_stop_payment.py @@ -0,0 +1,33 @@ +from unit.models import * +from unit.utils import date_utils + + +StopPaymentStatus = Literal["Active", "Disabled"] + +class CheckStopPaymentDTO(object): + def __init__(self, id: str, created_at: datetime, updated_at: datetime, amount: int, status: StopPaymentStatus, + check_number: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "checkStopPayment" + self.attributes = self.attributes = { + "createdAt": created_at, + "updatedAt": updated_at, + "amount": amount, + "status": status, + "checkNumber": check_number, + "tags": tags, + } + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckStopPaymentDTO( + id=_id, + created_at=date_utils.to_datetime(attributes["createdAt"]), + updated_at=date_utils.to_datetime(attributes["updatedAt"]), + amount=attributes["amount"], + status=attributes["status"], + check_number=attributes["checkNumber"], + tags=attributes.get("tags"), + relationships=relationships, + ) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 45bd1f6c..788f90d7 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -1,6 +1,10 @@ import json from unit.models import * from datetime import datetime, date + +from unit.models.batch_release import BatchReleaseDTO +from unit.models.check_payment import CheckPaymentDTO +from unit.models.reward import RewardDTO from unit.utils import date_utils from unit.models.applicationForm import ApplicationFormDTO from unit.models.application import IndividualApplicationDTO, BusinessApplicationDTO, ApplicationDocumentDTO @@ -9,8 +13,8 @@ from unit.models.card import IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO,\ BusinessVirtualDebitCardDTO, PinStatusDTO, CardLimitsDTO from unit.models.transaction import * -from unit.models.payment import AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, AchReceivedPaymentDTO -from unit.models.payment import AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, BillPaymentDTO +from unit.models.payment import AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, AchReceivedPaymentDTO, BillPaymentDTO, \ + SimulateIncomingAchPaymentDTO, PushToCardPaymentDTO from unit.models.customerToken import CustomerTokenDTO, CustomerVerificationTokenDTO from unit.models.fee import FeeDTO from unit.models.event import * @@ -24,6 +28,7 @@ from unit.models.authorization import AuthorizationDTO from unit.models.authorization_request import PurchaseAuthorizationRequestDTO from unit.models.account_end_of_day import AccountEndOfDayDTO +from unit.models.benificial_owner import BenificialOwnerDTO mappings = { "individualApplication": lambda _id, _type, attributes, relationships: @@ -110,8 +115,14 @@ "returnedCheckDepositTransaction": lambda _id, _type, attributes, relationships: ReturnedCheckDepositTransactionDTO.from_json_api(_id, _type, attributes, relationships), - "paymentAdvanceTransaction": lambda _id, _type, attributes, relationships: - PaymentAdvanceTransactionTransactionDTO.from_json_api(_id, _type, attributes, relationships), + "checkPaymentTransaction": lambda _id, _type, attributes, relationships: + CheckPaymentTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "returnedCheckPaymentTransaction": lambda _id, _type, attributes, relationships: + ReturnedCheckPaymentTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + #"paymentAdvanceTransaction": lambda _id, _type, attributes, relationships: + #PaymentAdvanceTransactionTransactionDTO.from_json_api(_id, _type, attributes, relationships), "repaidPaymentAdvanceTransaction": lambda _id, _type, attributes, relationships: RepaidPaymentAdvanceTransactionDTO.from_json_api(_id, _type, attributes, relationships), @@ -131,6 +142,63 @@ "achReceivedPayment": lambda _id, _type, attributes, relationships: AchReceivedPaymentDTO.from_json_api(_id, _type, attributes, relationships), + "checkPayment": lambda _id, _type, attributes, relationships: + CheckPaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "pushToCardPayment": lambda _id, _type, attributes, relationships: + PushToCardPaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.created": lambda _id, _type, attributes, relationships: + CheckPaymentCreatedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.markedForReturn": lambda _id, _type, attributes, relationships: + CheckPaymentMarkedForReturnEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.processed": lambda _id, _type, attributes, relationships: + CheckPaymentProcessedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.returned": lambda _id, _type, attributes, relationships: + CheckPaymentReturnedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.pending": lambda _id, _type, attributes, relationships: + CheckPaymentPendingEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.rejected": lambda _id, _type, attributes, relationships: + CheckPaymentRejectedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.inProduction": lambda _id, _type, attributes, relationships: + CheckPaymentInProductionEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.inDelivery": lambda _id, _type, attributes, relationships: + CheckPaymentInDeliveryEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.delivered": lambda _id, _type, attributes, relationships: + CheckPaymentDeliveredEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.returnToSender": lambda _id, _type, attributes, relationships: + CheckPaymentReturnToSenderEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.canceled": lambda _id, _type, attributes, relationships: + CheckPaymentCanceledEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.deliveryStatusChanged": lambda _id, _type, attributes, relationships: + CheckPaymentDeliveryStatusChangedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.additionalVerificationRequired": lambda _id, _type, attributes, relationships: + CheckPaymentAdditionalVerificationRequiredEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.additionalVerificationApproved": lambda _id, _type, attributes, relationships: + CheckPaymentAdditionalVerificationApprovedEvent.from_json_api(_id, _type, attributes, relationships), + + "stopPayment.created": lambda _id, _type, attributes, relationships: + StopPaymentCreatedEvent.from_json_api(_id, _type, attributes, relationships), + + "stopPayment.paymentStopped": lambda _id, _type, attributes, relationships: + StopPaymentPaymentStoppedEvent.from_json_api(_id, _type, attributes, relationships), + + "stopPayment.disabled": lambda _id, _type, attributes, relationships: + StopPaymentDisabledEvent.from_json_api(_id, _type, attributes, relationships), + "accountStatementDTO": lambda _id, _type, attributes, relationships: StatementDTO.from_json_api(_id, _type, attributes, relationships), @@ -176,6 +244,12 @@ "authorization.created": lambda _id, _type, attributes, relationships: AuthorizationCreatedEvent.from_json_api(_id, _type, attributes, relationships), + "authorization.canceled": lambda _id, _type, attributes, relationships: + AuthorizationCanceledEvent.from_json_api(_id, _type, attributes, relationships), + + "authorization.declined": lambda _id, _type, attributes, relationships: + AuthorizationDeclinedEvent.from_json_api(_id, _type, attributes, relationships), + "authorizationRequest.declined": lambda _id, _type, attributes, relationships: AuthorizationRequestDeclinedEvent.from_json_api(_id, _type, attributes, relationships), @@ -185,9 +259,6 @@ "authorizationRequest.approved": lambda _id, _type, attributes, relationships: AuthorizationRequestApprovedEvent.from_json_api(_id, _type, attributes, relationships), - "document.approved": lambda _id, _type, attributes, relationships: - DocumentApprovedEvent.from_json_api(_id, _type, attributes, relationships), - "document.rejected": lambda _id, _type, attributes, relationships: DocumentRejectedEvent.from_json_api(_id, _type, attributes, relationships), @@ -197,21 +268,42 @@ "checkDeposit.created": lambda _id, _type, attributes, relationships: CheckDepositCreatedEvent.from_json_api(_id, _type, attributes, relationships), + "checkDeposit.pendingReview": lambda _id, _type, attributes, relationships: + CheckDepositPendingReviewEvent.from_json_api(_id, _type, attributes, relationships), + + "checkDeposit.pending": lambda _id, _type, attributes, relationships: + CheckDepositPendingEvent.from_json_api(_id, _type, attributes, relationships), + "checkDeposit.clearing": lambda _id, _type, attributes, relationships: CheckDepositClearingEvent.from_json_api(_id, _type, attributes, relationships), "checkDeposit.sent": lambda _id, _type, attributes, relationships: CheckDepositSentEvent.from_json_api(_id, _type, attributes, relationships), + "checkDeposit.rejected": lambda _id, _type, attributes, relationships: + CheckDepositRejectedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkDeposit.returned": lambda _id, _type, attributes, relationships: + CheckDepositReturnedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkPayment.additionalVerificationRequired": lambda _id, _type, attributes, relationships: + CheckPaymentAdditionalVerificationRequiredEvent.from_json_api(_id, _type, attributes, relationships), + "payment.clearing": lambda _id, _type, attributes, relationships: PaymentClearingEvent.from_json_api(_id, _type, attributes, relationships), + "payment.created": lambda _id, _type, attributes, relationships: + PaymentCreatedEvent.from_json_api(_id, _type, attributes, relationships), + "payment.sent": lambda _id, _type, attributes, relationships: PaymentSentEvent.from_json_api(_id, _type, attributes, relationships), "payment.returned": lambda _id, _type, attributes, relationships: PaymentReturnedEvent.from_json_api(_id, _type, attributes, relationships), + "payment.rejected": lambda _id, _type, attributes, relationships: + PaymentRejectedEvent.from_json_api(_id, _type, attributes, relationships), + "statements.created": lambda _id, _type, attributes, relationships: StatementsCreatedEvent.from_json_api(_id, _type, attributes, relationships), @@ -221,6 +313,9 @@ "customer.created": lambda _id, _type, attributes, relationships: CustomerCreatedEvent.from_json_api(_id, _type, attributes, relationships), + "customer.updated": lambda _id, _type, attributes, relationships: + CustomerUpdatedEvent.from_json_api(_id, _type, attributes, relationships), + "account.reopened": lambda _id, _type, attributes, relationships: AccountReopenedEvent.from_json_api(_id, _type, attributes, relationships), @@ -253,7 +348,34 @@ "pinStatus": lambda _id, _type, attributes, relationships: PinStatusDTO.from_json_api(attributes), - } + + "beneficialOwner": lambda _id, _type, attributes, relationships: + BenificialOwnerDTO.from_json_api(attributes), + + "reward": lambda _id, _type, attributes, relationships: + RewardDTO.from_json_api(_id, attributes, relationships), + + "batchRelease": lambda _id, _type, attributes, relationships: + BatchReleaseDTO.from_json_api(_id, _type, attributes, relationships), + + "dispute.created": lambda _id, _type, attributes, relationships: + DisputeCreatedEvent.from_json_api(_id, _type, attributes, relationships), + + "dispute.statusChanged": lambda _id, _type, attributes, relationships: + DisputeStatusChangedEvent.from_json_api(_id, _type, attributes, relationships), + + "accountLowBalanceClosureTransaction": lambda _id, _type, attributes, relationships: + AccountLowBalanceClosureTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "negativeBalanceCoverageTransaction": lambda _id, _type, attributes, relationships: + NegativeBalanceCoverageTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "receivedPayment.created": lambda _id, _type, attributes, relationships: + ReceivedPaymentCreatedEvent.from_json_api(_id, _type, attributes, relationships), + + "writeOffTransaction": lambda _id, _type, attributes, relationships: + WriteOffTransactionDTO.from_json_api(_id, _type, attributes, relationships), +} def split_json_api_single_response(payload: Dict): @@ -314,6 +436,13 @@ def decode(payload): class UnitEncoder(json.JSONEncoder): def default(self, obj): + if isinstance(obj, CardLevelLimits): + return { + "dailyWithdrawal": obj.daily_withdrawal, + "dailyPurchase": obj.daily_purchase, + "monthlyWithdrawal": obj.monthly_withdrawal, + "monthlyPurchase": obj.monthly_purchase + } if isinstance(obj, FullName): return {"first": obj.first, "last": obj.last} if isinstance(obj, Phone): @@ -332,6 +461,11 @@ def default(self, obj): return addr if isinstance(obj, BusinessContact): return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} + if isinstance(obj, AuthorizedUser): + authorized_user = {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} + if obj.jwt_subject is not None: + authorized_user["jwtSubject"] = obj.jwt_subject + return authorized_user if isinstance(obj, Officer): officer = {"fullName": obj.full_name, "dateOfBirth": date_utils.to_date_str(obj.date_of_birth), "address": obj.address, "phone": obj.phone, "email": obj.email} @@ -345,6 +479,14 @@ def default(self, obj): officer["passport"] = obj.passport if obj.nationality is not None: officer["nationality"] = obj.nationality + if obj.id_theft_score is not None: + officer["idTheftScore"] = obj.id_theft_score + if obj.occupation is not None: + officer["occupation"] = obj.occupation + if obj.annual_income is not None: + officer["annualIncome"] = obj.annual_income + if obj.source_of_income is not None: + officer["sourceOfIncome"] = obj.source_of_income return officer if isinstance(obj, BeneficialOwner): beneficial_owner = {"fullName": obj.full_name, "dateOfBirth": date_utils.to_date_str(obj.date_of_birth), @@ -359,6 +501,12 @@ def default(self, obj): beneficial_owner["nationality"] = obj.nationality if obj.percentage is not None: beneficial_owner["percentage"] = obj.percentage + if obj.occupation is not None: + beneficial_owner["occupation"] = obj.occupation + if obj.annual_income is not None: + beneficial_owner["annualIncome"] = obj.annual_income + if obj.source_of_income is not None: + beneficial_owner["sourceOfIncome"] = obj.source_of_income return beneficial_owner if isinstance(obj, RelationshipArray): return {"data": list(map(lambda r: r.to_dict(), obj.relationships))} @@ -369,4 +517,9 @@ def default(self, obj): "accountType": obj.account_type, "name": obj.name} if isinstance(obj, Coordinates): return {"longitude": obj.longitude, "latitude": obj.latitude} + if isinstance(obj, CheckPaymentCounterparty): + return { + "name": obj.name, + "address": obj.address + } return json.JSONEncoder.default(self, obj) diff --git a/unit/models/counterparty.py b/unit/models/counterparty.py index c91ecb8d..987c4ba7 100644 --- a/unit/models/counterparty.py +++ b/unit/models/counterparty.py @@ -23,7 +23,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes["accountType"], attributes["type"], attributes["permissions"], relationships) -class CreateCounterpartyRequest(object): +class CreateCounterpartyRequest(UnitRequest): def __init__(self, name: str, routing_number: str, account_number: str, account_type: str, type: str, relationships: [Dict[str, Relationship]], tags: Optional[object] = None, idempotency_key: Optional[str] = None): @@ -156,11 +156,14 @@ def from_json_api(_id, _type, attributes, relationships): class ListCounterpartyParams(UnitParams): def __init__(self, offset: int = 0, limit: int = 100, customer_id: Optional[str] = None, - tags: Optional[object] = None): + tags: Optional[object] = None, account_number: Optional[str] = None, + routing_number: Optional[str] = None): self.offset = offset self.limit = limit self.customer_id = customer_id self.tags = tags + self.account_number = account_number + self.routing_number = routing_number def to_dict(self) -> Dict: parameters = {"page[limit]": self.limit, "page[offset]": self.offset} @@ -168,5 +171,9 @@ def to_dict(self) -> Dict: parameters["filter[customerId]"] = self.customer_id if self.tags: parameters["filter[tags]"] = self.tags + if self.account_number: + parameters["filter[accountNumber]"] = self.account_number + if self.routing_number: + parameters["filter[routingNumber]"] = self.routing_number return parameters diff --git a/unit/models/customer.py b/unit/models/customer.py index 4d9e7e34..d1ceab7b 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -1,16 +1,23 @@ from unit.utils import date_utils from unit.models import * +ArchiveReason = Literal["Inactive", "FraudACHActivity", "FraudCardActivity", "FraudCheckActivity", + "FraudApplicationHistory", "FraudAccountActivity", "FraudClientIdentified"] + +CustomerStatus = Literal["Active", "Archived"] class IndividualCustomerDTO(object): def __init__(self, id: str, created_at: datetime, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, ssn: Optional[str], passport: Optional[str], nationality: Optional[str], - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + authorized_users: [AuthorizedUser], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]], status: CustomerStatus, + archive_reason: Optional[ArchiveReason]): self.id = id self.type = 'individualCustomer' self.attributes = {"createdAt": created_at, "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, "phone": phone, "email": email, "ssn": ssn, "passport": passport, - "nationality": nationality, "tags": tags} + "nationality": nationality, "authorizedUsers": authorized_users, "tags": tags, + "status": status, "archiveReason": archive_reason} self.relationships = relationships @staticmethod @@ -20,7 +27,8 @@ def from_json_api(_id, _type, attributes, relationships): FullName.from_json_api(attributes["fullName"]), date_utils.to_date(attributes["dateOfBirth"]), Address.from_json_api(attributes["address"]), Phone.from_json_api(attributes["phone"]), attributes["email"], attributes.get("ssn"), attributes.get("passport"), attributes.get("nationality"), - attributes.get("tags"), relationships + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("tags"), relationships, + attributes.get("status"), attributes.get("archiveReason") ) @@ -28,12 +36,14 @@ class BusinessCustomerDTO(object): def __init__(self, id: str, created_at: datetime, name: str, address: Address, phone: Phone, state_of_incorporation: str, ein: str, entity_type: EntityType, contact: BusinessContact, authorized_users: [AuthorizedUser], dba: Optional[str], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + relationships: Optional[Dict[str, Relationship]], status: CustomerStatus, + archive_reason: Optional[ArchiveReason]): self.id = id self.type = 'businessCustomer' self.attributes = {"createdAt": created_at, "name": name, "address": address, "phone": phone, "stateOfIncorporation": state_of_incorporation, "ein": ein, "entityType": entity_type, - "contact": contact, "authorizedUsers": authorized_users, "dba": dba, "tags": tags} + "contact": contact, "authorizedUsers": authorized_users, "dba": dba, "tags": tags, + "status": status, "archiveReason": archive_reason} self.relationships = relationships @staticmethod @@ -43,20 +53,23 @@ def from_json_api(_id, _type, attributes, relationships): Address.from_json_api(attributes["address"]), Phone.from_json_api(attributes["phone"]), attributes["stateOfIncorporation"], attributes["ein"], attributes["entityType"], BusinessContact.from_json_api(attributes["contact"]), - [AuthorizedUser.from_json_api(user) for user in attributes["authorizedUsers"]], - attributes.get("dba"), attributes.get("tags"), relationships) + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), + attributes.get("dba"), attributes.get("tags"), relationships, attributes.get("status"), + attributes.get("archiveReason")) CustomerDTO = Union[IndividualCustomerDTO, BusinessCustomerDTO] class PatchIndividualCustomerRequest(UnitRequest): def __init__(self, customer_id: str, address: Optional[Address] = None, phone: Optional[Phone] = None, - email: Optional[str] = None, dba: Optional[str] = None, tags: Optional[Dict[str, str]] = None): + email: Optional[str] = None, dba: Optional[str] = None, + authorized_users: Optional[List[AuthorizedUser]] = None, tags: Optional[Dict[str, str]] = None): self.customer_id = customer_id self.address = address self.phone = phone self.email = email self.dba = dba + self.authorized_users = authorized_users self.tags = tags def to_json_api(self) -> Dict: @@ -79,6 +92,9 @@ def to_json_api(self) -> Dict: if self.dba: payload["data"]["attributes"]["dba"] = self.dba + if self.authorized_users: + payload["data"]["attributes"]["authorizedUsers"] = self.authorized_users + if self.tags: payload["data"]["attributes"]["tags"] = self.tags @@ -150,3 +166,24 @@ def to_dict(self) -> Dict: parameters["sort"] = self.sort return parameters + +class ArchiveCustomerRequest(UnitRequest): + def __init__(self, customer_id: str, reason: Optional[ArchiveReason] = None): + self.customer_id = customer_id + self.reason = reason + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "archiveCustomer", + "attributes": {} + } + } + + if self.reason: + payload["data"]["attributes"]["reason"] = self.reason + + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) diff --git a/unit/models/customerToken.py b/unit/models/customerToken.py index 856ab64a..b8a5b153 100644 --- a/unit/models/customerToken.py +++ b/unit/models/customerToken.py @@ -21,12 +21,13 @@ def from_json_api(_id, _type, attributes, relationships): class CreateCustomerToken(UnitRequest): def __init__(self, customer_id: str, scope: str, verification_token: Optional[str] = None, - verification_code: Optional[str] = None, expires_in: Optional[int] = None): + verification_code: Optional[str] = None, expires_in: Optional[int] = None, jwt_token: Optional[str] = None): self.customer_id = customer_id self.scope = scope self.verification_token = verification_token self.verification_code = verification_code self.expires_in = expires_in + self.jwt_token = jwt_token def to_json_api(self) -> Dict: payload = { @@ -47,6 +48,9 @@ def to_json_api(self) -> Dict: if self.verification_code: payload["data"]["attributes"]["verificationCode"] = self.verification_code + if self.jwt_token: + payload["data"]["attributes"]["jwtToken"] = self.jwt_token + return payload def __repr__(self): diff --git a/unit/models/dispute.py b/unit/models/dispute.py new file mode 100644 index 00000000..ca3f6fb8 --- /dev/null +++ b/unit/models/dispute.py @@ -0,0 +1,54 @@ +from unit.utils import date_utils +from unit.models import * + +DisputeStatus = Literal[ + "InvestigationStarted", + "ProvisionallyCredited", + "Denied", + "ResolvedLost", + "ResolvedWon", +] + + +class DisputeDTO(object): + def __init__( + self, + id: str, + source: str, + status_history: List[Dict[str, str]], + status: DisputeStatus, + description: str, + dispute_type: str, + created_at: datetime, + amount: int, + decision_reason: Optional[str], + relationships: Optional[Dict[str, Relationship]], + ): + self.id = id + self.type = "dispute" + self.attributes = { + "createdAt": created_at, + "source": source, + "statusHistory": status_history, + "status": status, + "description": description, + "disputeType": dispute_type, + "amount": amount, + "decisionReason": decision_reason, + } + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DisputeDTO( + _id, + date_utils.to_datetime(attributes["createdAt"]), + attributes["source"], + attributes["statusHistory"], + attributes["status"], + attributes["description"], + attributes["disputeType"], + attributes["amount"], + attributes.get("decisionReason"), + relationships, + ) diff --git a/unit/models/event.py b/unit/models/event.py index cf922d1d..191a3ddf 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -1,6 +1,8 @@ import json from datetime import datetime, date from typing import Literal, Optional + +from unit.models.check_stop_payment import StopPaymentStatus from unit.utils import date_utils from unit.models import * @@ -73,20 +75,57 @@ def from_json_api(_id, _type, attributes, relationships): return ApplicationAwaitingDocumentsEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), relationships) +class AuthorizationCanceledEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, + recurring: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'authorization.canceled' + self.attributes["cardLast4Digits"] = card_last_4_digits + self.attributes["amount"] = amount + self.attributes["recurring"] = recurring + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AuthorizationCanceledEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["amount"], attributes["cardLast4Digits"], + attributes["recurring"], attributes.get("tags"), + relationships) + +class AuthorizationDeclinedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, merchant: Merchant, + reason: str, recurring: str, tags: Optional[Dict[str, str]] = None, + relationships: Optional[Dict[str, Relationship]] = None): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'authorization.declined' + self.attributes["cardLast4Digits"] = card_last_4_digits + self.attributes["amount"] = amount + self.attributes["reason"] = reason + self.attributes["merchant"] = merchant + self.attributes["recurring"] = recurring + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AuthorizationDeclinedEvent( + id=_id, created_at=date_utils.to_datetime(attributes["createdAt"]), + amount=attributes["amount"], card_last_4_digits=attributes["cardLast4Digits"], + merchant=Merchant.from_json_api(attributes["merchant"]), reason=attributes.get("reason"), recurring=attributes["recurring"], + tags=attributes.get("tags"), relationships=relationships) class AuthorizationCreatedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, card_last_4_digits: str, recurring: str, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, merchant: Merchant, + recurring: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseEvent.__init__(self, id, created_at, tags, relationships) self.type = 'authorization.created' self.attributes["cardLast4Digits"] = card_last_4_digits + self.attributes["amount"] = amount + self.attributes["merchant"] = merchant self.attributes["recurring"] = recurring @staticmethod def from_json_api(_id, _type, attributes, relationships): return AuthorizationCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["cardLast4Digits"], attributes["recurring"], - attributes.get("tags"), relationships) + attributes["amount"], attributes["cardLast4Digits"], Merchant.from_json_api(attributes["merchant"]), + attributes["recurring"], attributes.get("tags"), relationships) class AuthorizationRequestApprovedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, amount: str, status: str, approved_amount: str, @@ -112,8 +151,8 @@ def from_json_api(_id, _type, attributes, relationships): class AuthorizationRequestDeclinedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, amount: str, status: str, decline_reason: str, - partial_approval_allowed: str, merchant: Dict[str, str], recurring: str, + def __init__(self, id: str, created_at: datetime, amount: str, status: Optional[str], decline_reason: str, + partial_approval_allowed: str, merchant: Optional[Dict[str, str]], recurring: Optional[str], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseEvent.__init__(self, id, created_at, tags, relationships) self.type = 'authorizationRequest.declined' @@ -128,20 +167,23 @@ def __init__(self, id: str, created_at: datetime, amount: str, status: str, decl @staticmethod def from_json_api(_id, _type, attributes, relationships): return AuthorizationRequestDeclinedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["amount"], attributes["status"], + attributes["amount"], attributes.get("status"), attributes["declineReason"], attributes["partialApprovalAllowed"], - attributes["merchant"], attributes["recurring"], + attributes.get("merchant"), attributes.get("recurring"), attributes.get("tags"), relationships) class AuthorizationRequestPendingEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, amount: str, status: str, partial_approval_allowed: str, - merchant: Dict[str, str], recurring: str, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + def __init__(self, id: str, created_at: datetime, amount: int, status: str, partial_approval_allowed: str, + card_present: bool, digital_wallet: str, ecommerce: bool, merchant: Merchant, recurring: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseEvent.__init__(self, id, created_at, tags, relationships) self.type = 'authorizationRequest.pending' self.attributes["amount"] = amount self.attributes["status"] = status + self.attributes["cardPresent"] = card_present + self.attributes["digitalWallet"] = digital_wallet + self.attributes["ecommerce"] = ecommerce self.attributes["partialApprovalAllowed"] = partial_approval_allowed self.attributes["merchant"] = merchant self.attributes["recurring"] = recurring @@ -149,10 +191,20 @@ def __init__(self, id: str, created_at: datetime, amount: str, status: str, part @staticmethod def from_json_api(_id, _type, attributes, relationships): - return AuthorizationRequestPendingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["amount"], attributes["status"], - attributes["partialApprovalAllowed"], attributes["merchant"], - attributes["recurring"], attributes.get("tags"), relationships) + return AuthorizationRequestPendingEvent( + id=_id, + created_at=date_utils.to_datetime(attributes["createdAt"]), + amount=attributes["amount"], + status=attributes["status"], + ecommerce=attributes.get("ecommerce"), + card_present=attributes.get("cardPresent"), + digital_wallet=attributes.get("digitalWallet"), + partial_approval_allowed=attributes["partialApprovalAllowed"], + merchant=Merchant.from_json_api(attributes["merchant"]), + recurring=attributes["recurring"], + tags=attributes.get("tags"), + relationships=relationships + ) class CardActivatedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], @@ -180,6 +232,96 @@ def from_json_api(_id, _type, attributes, relationships): attributes["previousStatus"], attributes.get("tags"), relationships) +class CardFraudCaseCreatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, decision: str, activity_type: str, + expires_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'cardFraudCase.created' + self.attributes["status"] = status + self.attributes["decision"] = decision + self.attributes["activityType"] = activity_type + self.attributes["expiresAt"] = expires_at + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardFraudCaseCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["decision"], attributes["activityType"], + date_utils.to_datetime(attributes["expiresAt"]), attributes.get("tags"), + relationships) + + +class CardFraudCaseActivatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, decision: str, activity_type: str, + expires_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'cardFraudCase.activated' + self.attributes["status"] = status + self.attributes["decision"] = decision + self.attributes["activityType"] = activity_type + self.attributes["expiresAt"] = expires_at + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardFraudCaseActivatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["decision"], attributes["activityType"], + date_utils.to_datetime(attributes["expiresAt"]), attributes.get("tags"), + relationships) + + +class CardFraudCaseExpiredEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, decision: str, activity_type: str, + expires_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'cardFraudCase.expired' + self.attributes["status"] = status + self.attributes["decision"] = decision + self.attributes["activityType"] = activity_type + self.attributes["expiresAt"] = expires_at + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardFraudCaseExpiredEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["decision"], attributes["activityType"], + date_utils.to_datetime(attributes["expiresAt"]), attributes.get("tags"), + relationships) + + +class CardFraudCaseFraudEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, decision: str, activity_type: str, + expires_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'cardFraudCase.fraud' + self.attributes["status"] = status + self.attributes["decision"] = decision + self.attributes["activityType"] = activity_type + self.attributes["expiresAt"] = expires_at + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardFraudCaseFraudEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["decision"], attributes["activityType"], + date_utils.to_datetime(attributes["expiresAt"]), attributes.get("tags"), + relationships) + + +class CardFraudCaseNoFraudEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, decision: str, activity_type: str, + expires_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'cardFraudCase.noFraud' + self.attributes["status"] = status + self.attributes["decision"] = decision + self.attributes["activityType"] = activity_type + self.attributes["expiresAt"] = expires_at + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardFraudCaseNoFraudEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["decision"], attributes["activityType"], + date_utils.to_datetime(attributes["expiresAt"]), attributes.get("tags"), + relationships) + + class CheckDepositCreatedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, status: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -192,6 +334,33 @@ def from_json_api(_id, _type, attributes, relationships): return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], attributes.get("tags"), relationships) + +class CheckDepositPendingReviewEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkDeposit.pendingReview' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositPendingReviewEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckDepositPendingEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkDeposit.pending' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositPendingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes.get("tags"), relationships) + + class CheckDepositClearingEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -218,6 +387,21 @@ def from_json_api(_id, _type, attributes, relationships): attributes["previousStatus"], attributes.get("tags"), relationships) +class CheckDepositRejectedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, reason: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkDeposit.rejected' + self.attributes["previousStatus"] = previous_status + self.attributes["reason"] = reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositRejectedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes["reason"], attributes.get("tags"), relationships) + + + class CheckDepositReturnedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -231,6 +415,216 @@ def from_json_api(_id, _type, attributes, relationships): attributes["previousStatus"], attributes.get("tags"), relationships) +class CheckPaymentCreatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, additional_verification_required: bool, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.created' + self.attributes["status"] = status + self.attributes["additionalVerificationRequired"] = additional_verification_required + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["additionalVerificationRequired"], + attributes.get("tags"), relationships) + + +class CheckPaymentMarkedForReturnEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.markedForReturn' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentMarkedForReturnEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckPaymentProcessedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, additional_verification_required: bool, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.processed' + self.attributes["previousStatus"] = previous_status + self.attributes["additionalVerificationRequired"] = additional_verification_required + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentProcessedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes["additionalVerificationRequired"], + attributes.get("tags"), relationships) + + +class CheckPaymentReturnedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.returned' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckPaymentPendingEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, + counterparty_moved: Optional[bool], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.pending' + self.attributes["status"] = status + self.attributes["previousStatus"] = previous_status + self.attributes["counterpartyMoved"] = counterparty_moved + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentPendingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["previousStatus"], + attributes.get("counterpartyMoved"), attributes.get("tags"), relationships) + + +class CheckPaymentRejectedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, + reject_reason: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.rejected' + self.attributes["status"] = status + self.attributes["previousStatus"] = previous_status + self.attributes["rejectReason"] = reject_reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentRejectedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["previousStatus"], + attributes["rejectReason"], attributes.get("tags"), relationships) + + +class CheckPaymentInProductionEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.inProduction' + self.attributes["status"] = status + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentInProductionEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckPaymentInDeliveryEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, delivery_status: str, + tracked_at: datetime, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.inDelivery' + self.attributes["status"] = status + self.attributes["previousStatus"] = previous_status + self.attributes["deliveryStatus"] = delivery_status + self.attributes["trackedAt"] = tracked_at + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentInDeliveryEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["previousStatus"], attributes["deliveryStatus"], + attributes["trackedAt"], attributes.get("tags"), relationships) + + +class CheckPaymentDeliveredEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.delivered' + self.attributes["status"] = status + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentDeliveredEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckPaymentReturnToSenderEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.returnToSender' + self.attributes["status"] = status + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentReturnToSenderEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckPaymentCanceledEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.canceled' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentCanceledEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckPaymentDeliveryStatusChangedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_delivery_status: str, new_delivery_status: str, + tracked_at: datetime, postal_code: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.deliveryStatusChanged' + self.attributes["previousDeliveryStatus"] = previous_delivery_status + self.attributes["newDeliveryStatus"] = new_delivery_status + self.attributes["trackedAt"] = tracked_at + self.attributes["postalCode"] = postal_code + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentDeliveryStatusChangedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousDeliveryStatus"], + attributes["newDeliveryStatus"], attributes["trackedAt"], + attributes["postalCode"], attributes.get("tags"), relationships) + + +class CheckPaymentAdditionalVerificationRequiredEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, amount: int, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.additionalVerificationRequired' + self.attributes["status"] = status + self.attributes["amount"] = amount + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentAdditionalVerificationRequiredEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["amount"], attributes.get("tags"), relationships) + + +class CheckPaymentAdditionalVerificationApprovedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, amount: int, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkPayment.additionalVerificationApproved' + self.attributes["status"] = status + self.attributes["amount"] = amount + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentAdditionalVerificationApprovedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["amount"], attributes.get("tags"), relationships) + + class CustomerCreatedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -242,6 +636,19 @@ def from_json_api(_id, _type, attributes, relationships): return CustomerCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), relationships) + +class CustomerUpdatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'customer.updated' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CustomerUpdatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes.get("tags"), relationships) + + class DocumentApprovedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -267,6 +674,29 @@ def from_json_api(_id, _type, attributes, relationships): attributes["reason"], attributes["reasonCode"], attributes.get("tags"), relationships) +class PaymentCreatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'payment.created' + self.attributes["status"] = status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes.get("tags"), relationships) + +class PaymentRejectedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'payment.rejected' + self.attributes["status"] = status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes.get("tags"), relationships) class PaymentClearingEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], @@ -304,6 +734,18 @@ def from_json_api(_id, _type, attributes, relationships): return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], attributes.get("tags"), relationships) +class PaymentRejectedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, reason: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'payment.rejected' + self.attributes["reason"] = reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentRejectedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["reason"], + attributes.get("tags"), relationships) + class StatementsCreatedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, period: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -317,7 +759,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("tags"), relationships) class TransactionCreatedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, summary: str, direction: str, amount: str, + def __init__(self, id: str, created_at: datetime, summary: str, direction: str, amount: int, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseEvent.__init__(self, id, created_at, tags, relationships) self.type = 'transaction.created' @@ -342,23 +784,159 @@ def from_json_api(_id, _type, attributes, relationships): return AccountReopenedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), relationships) -EventDTO = Union[AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, - ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, - AuthorizationCreatedEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, - AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, - CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, - CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, - PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject] +class StopPaymentCreatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'stopPayment.created' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return StopPaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), + relationships) + + +class StopPaymentPaymentStoppedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, stopped_payment_type: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.attributes['stoppedPaymentType'] = stopped_payment_type + self.type = 'stopPayment.paymentStopped' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return StopPaymentPaymentStoppedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["stoppedPaymentType"], attributes.get("tags"), + relationships) + + +class StopPaymentDisabledEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, status: str, previous_status: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.attributes['status'] = status + self.attributes['previousStatus'] = previous_status + self.type = 'stopPayment.disabled' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return StopPaymentDisabledEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["previousStatus"], attributes.get("tags"), relationships) + +class DisputeCreatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, amount: int, description: str, source: str, status: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.attributes['amount'] = amount + self.attributes['description'] = description + self.attributes['source'] = source + self.attributes['status'] = status + self.type = 'dispute.created' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DisputeCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["amount"], + attributes["description"], attributes["source"], attributes["status"], + attributes.get("tags"), relationships) + +class DisputeStatusChangedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, previous_status: str, new_status: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.attributes['previousStatus'] = previous_status + self.attributes['newStatus'] = new_status + self.type = 'dispute.statusChanged' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DisputeStatusChangedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + attributes["newStatus"], attributes.get("tags"), relationships) + +class ReceivedPaymentCreatedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, + status: str, + type: str, + amount: int, + completion_date: date, + company_name: str, + counterparty_routing_number: str, + description: str, + trace_number: str, + sec_code: str, + return_cutoff_time: Optional[datetime], + can_be_reprocessed: Optional[bool], + addenda: Optional[str], + tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.attributes["status"] = status + self.attributes["type"] = type + self.attributes["amount"] = amount + self.attributes["completionDate"] = completion_date + self.attributes["companyName"] = company_name + self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number + self.attributes["description"] = description + self.attributes["traceNumber"] = trace_number + self.attributes["secCode"] = sec_code + self.attributes["returnCutoffTime"] = return_cutoff_time + self.attributes["canBeReprocessed"] = can_be_reprocessed + self.attributes["addenda"] = addenda + + self.type = 'receivedPayment.created' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReceivedPaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["type"], attributes["amount"], + attributes["completionDate"], attributes["companyName"], + attributes["counterpartyRoutingNumber"], attributes["description"], + attributes["traceNumber"], attributes["secCode"], + attributes.get("returnCutoffTime"), attributes.get("canBeReprocessed"), + attributes.get("addenda"), attributes.get("tags"), relationships) + + +EventDTO = Union[ + AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, + ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, + CardFraudCaseCreatedEvent, CardFraudCaseActivatedEvent, CardFraudCaseExpiredEvent, + CardFraudCaseFraudEvent, CardFraudCaseNoFraudEvent, + AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, + AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, + AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, + CheckDepositCreatedEvent, CheckDepositPendingReviewEvent, CheckDepositPendingEvent, + CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositRejectedEvent, CheckDepositReturnedEvent, + CheckPaymentCreatedEvent, CheckPaymentMarkedForReturnEvent, CheckPaymentProcessedEvent, CheckPaymentReturnedEvent, + CheckPaymentPendingEvent, CheckPaymentRejectedEvent, CheckPaymentInProductionEvent, CheckPaymentInDeliveryEvent, + CheckPaymentDeliveredEvent, CheckPaymentReturnToSenderEvent, CheckPaymentCanceledEvent, + CheckPaymentDeliveryStatusChangedEvent, CheckPaymentAdditionalVerificationRequiredEvent, + CheckPaymentAdditionalVerificationApprovedEvent, + CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, + StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, + StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, + DisputeCreatedEvent, DisputeStatusChangedEvent, ReceivedPaymentCreatedEvent +] class ListEventParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, type: Optional[List[str]] = None): + def __init__( + self, + limit: int = 100, + offset: int = 0, + type: Optional[str] = None, + since: Optional[str] = None, + until: Optional[str] = None, + ): self.limit = limit self.offset = offset self.type = type + self.since = since + self.until = until def to_dict(self) -> Dict: parameters = {"page[limit]": self.limit, "page[offset]": self.offset} if self.type: parameters["filter[type][]"] = self.type + if self.since: + parameters["filter[since]"] = self.since + if self.until: + parameters["filter[until]"] = self.until return parameters diff --git a/unit/models/fee.py b/unit/models/fee.py index 296ce187..efbf2e0d 100644 --- a/unit/models/fee.py +++ b/unit/models/fee.py @@ -38,7 +38,7 @@ def to_json_api(self) -> Dict: } if self.idempotency_key: - payload["data"]["attributes"]["idempotencyKey"] = self.tags + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key if self.tags: payload["data"]["attributes"]["tags"] = self.tags diff --git a/unit/models/payment.py b/unit/models/payment.py index 2548d980..971b1731 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -5,15 +5,20 @@ PaymentDirections = Literal["Debit", "Credit"] PaymentStatus = Literal["Pending", "Rejected", "Clearing", "Sent", "Canceled", "Returned"] + class BasePayment(object): def __init__(self, id: str, created_at: datetime, status: PaymentStatus, direction: PaymentDirections, description: str, amount: int, reason: Optional[str], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + relationships: Optional[Dict[str, Relationship]], astra_routine_id: Optional[str] = None): self.id = id self.attributes = {"createdAt": created_at, "status": status, "direction": direction, "description": description, "amount": amount, "reason": reason, "tags": tags} self.relationships = relationships + if astra_routine_id: + self.attributes["astraRoutineId"] = astra_routine_id + + class AchPaymentDTO(BasePayment): def __init__(self, id: str, created_at: datetime, status: PaymentStatus, counterparty: Counterparty, direction: str, description: str, amount: int, addenda: Optional[str], reason: Optional[str], @@ -33,6 +38,53 @@ def from_json_api(_id, _type, attributes, relationships): attributes["amount"], attributes.get("addenda"), attributes.get("reason"), settlement_date, attributes.get("tags"), relationships) +class SimulateIncomingAchPaymentDTO(BasePayment): + def __init__(self, id: str, created_at: datetime, status: PaymentStatus, direction: str, + description: str, amount: int, reason: Optional[str], + settlement_date: Optional[datetime], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BasePayment.__init__(self, id, created_at, direction, description, amount, reason, tags, relationships) + self.type = 'achPayment' + self.attributes["status"] = status + self.settlement_date = settlement_date + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BasePayment( + _id, + date_utils.to_datetime(attributes["createdAt"]), + attributes.get("direction"), + attributes["description"], + attributes["amount"], + attributes.get("reason"), + attributes.get("tags"), + relationships + ) + +class SimulateAchPaymentDTO(object): + def __init__(self, id: str, created_at: datetime, status: PaymentStatus, direction: str, + description: str, amount: int, reason: Optional[str], + settlement_date: Optional[datetime], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BasePayment.__init__(self, id, created_at, direction, description, amount, reason, tags, relationships) + self.type = 'achPayment' + self.attributes["status"] = status + self.settlement_date = settlement_date + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BasePayment( + _id, + date_utils.to_datetime(attributes["createdAt"]), + attributes.get("status"), + attributes.get("direction"), + attributes["description"], + attributes["amount"], + attributes.get("reason"), + attributes.get("tags"), + relationships + ) + class BookPaymentDTO(BasePayment): def __init__(self, id: str, created_at: datetime, status: PaymentStatus, direction: Optional[str], description: str, amount: int, reason: Optional[str], tags: Optional[Dict[str, str]], @@ -61,10 +113,26 @@ def from_json_api(_id, _type, attributes, relationships): attributes["description"], attributes["amount"], attributes.get("reason"), attributes.get("tags"), relationships) + +class PushToCardPaymentDTO(BasePayment): + def __init__(self, id: str, created_at: datetime, status: PaymentStatus, direction: Optional[str], description: str, + amount: int, astra_routine_id: str, reason: Optional[str], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BasePayment.__init__(self, id, created_at, status, direction, description, amount, reason, tags, relationships, astra_routine_id) + self.type = 'pushToCardPayment' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PushToCardPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes.get("direction"), attributes["description"], attributes["amount"], + attributes.get("astraRoutineId"), attributes.get("reason"), attributes.get("tags"), + relationships) + + class BillPaymentDTO(BasePayment): def __init__(self, id: str, created_at: datetime, status: PaymentStatus, direction: str, description: str, amount: int, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): - BasePayment.__init__(self, id, created_at, status, direction, description, amount, reason, tags, relationships) + BasePayment.__init__(self, id, created_at, status, direction, description, amount, tags, relationships) self.type = 'billPayment' @staticmethod @@ -81,13 +149,15 @@ class AchReceivedPaymentDTO(object): def __init__(self, id: str, created_at: datetime, status: AchReceivedPaymentStatus, was_advanced: bool, completion_date: datetime, return_reason: Optional[str], amount: int, description: str, addenda: Optional[str], company_name: str, counterparty_routing_number: str, trace_number: str, - sec_code: Optional[str], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + sec_code: Optional[str], return_cutoff_time: Optional[datetime], can_be_reprocessed: Optional[bool], + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): self.type = "achReceivedPayment" self.attributes = {"createdAt": created_at, "status": status, "wasAdvanced": was_advanced, "completionDate": completion_date, "returnReason": return_reason, "description": description, "amount": amount, "addenda": addenda, "companyName": company_name, "counterpartyRoutingNumber": counterparty_routing_number, "traceNumber": trace_number, - "secCode": sec_code, "tags": tags} + "secCode": sec_code, "returnCutoffTime": return_cutoff_time, "canBeReprocessed": can_be_reprocessed, + "tags": tags} self.relationships = relationships @staticmethod @@ -97,19 +167,22 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("returnReason"),attributes["amount"], attributes["description"], attributes.get("addenda"), attributes.get("companyName"), attributes.get("counterpartyRoutingNumber"), attributes.get("traceNumber"), - attributes.get("secCode"), attributes.get("tags"), relationships) + attributes.get("secCode"), attributes.get("returnCutoffTime"), attributes.get("canBeReprocessed"), + attributes.get("tags"), relationships) class CreatePaymentBaseRequest(UnitRequest): def __init__(self, amount: int, description: str, relationships: Dict[str, Relationship], - idempotency_key: Optional[str], tags: Optional[Dict[str, str]], direction: str = "Credit", - type: str = "achPayment"): + idempotency_key: Optional[str], tags: Optional[Dict[str, str]], direction: Optional[str], + type: str = "achPayment", same_day: Optional[bool] = False, configuration: dict = None): self.type = type self.amount = amount + self.same_day = same_day self.description = description self.direction = direction self.idempotency_key = idempotency_key self.tags = tags self.relationships = relationships + self.configuration = configuration def to_json_api(self) -> Dict: payload = { @@ -117,19 +190,27 @@ def to_json_api(self) -> Dict: "type": self.type, "attributes": { "amount": self.amount, - "direction": self.direction, "description": self.description }, "relationships": self.relationships } } + if self.direction: + payload["data"]["attributes"]["direction"] = self.direction + if self.idempotency_key: payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + + if self.same_day: + payload["data"]["attributes"]["sameDay"] = self.same_day if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.configuration: + payload["data"]["attributes"]["configuration"] = self.configuration + return payload def __repr__(self): @@ -138,8 +219,8 @@ def __repr__(self): class CreateInlinePaymentRequest(CreatePaymentBaseRequest): def __init__(self, amount: int, description: str, counterparty: Counterparty, relationships: Dict[str, Relationship], addenda: Optional[str], idempotency_key: Optional[str], tags: Optional[Dict[str, str]], - direction: str = "Credit"): - CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction) + direction: str = "Credit", same_day: Optional[bool] = False): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day=same_day) self.counterparty = counterparty self.addenda = addenda @@ -156,8 +237,8 @@ def to_json_api(self) -> Dict: class CreateLinkedPaymentRequest(CreatePaymentBaseRequest): def __init__(self, amount: int, description: str, relationships: Dict[str, Relationship], addenda: Optional[str], verify_counterparty_balance: Optional[bool], idempotency_key: Optional[str], - tags: Optional[Dict[str, str]], direction: str = "Credit"): - CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction) + tags: Optional[Dict[str, str]], direction: str = "Credit", same_day: Optional[bool] = False): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day=same_day) self.addenda = addenda self.verify_counterparty_balance = verify_counterparty_balance @@ -172,6 +253,68 @@ def to_json_api(self) -> Dict: return payload +class SimulateIncomingAchRequest(CreatePaymentBaseRequest): + def __init__( + self, amount: int, description: str, + relationships: Dict[str, Relationship], + direction: str = "Credit" + ): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, None, None, direction) + self.verify_counterparty_balance = False + + def to_json_api(self) -> Dict: + payload = CreatePaymentBaseRequest.to_json_api(self) + + return payload + +class SimulateTransmitAchRequest(UnitRequest): + def __init__( + self, payment_id: int + ): + self.type = "transmitAchPayment" + self.id = payment_id + self.relationships = { + "payment": { + "data": { + "type": "achPayment", + "id": self.id + } + } + } + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self.type, + "relationships": self.relationships + } + } + return payload + +class SimulateClearAchRequest(UnitRequest): + def __init__( + self, payment_id: int + ): + self.type = "clearAchPayment" + self.id = payment_id + self.relationships = { + "payment": { + "data": { + "type": "achPayment", + "id": self.id + } + } + } + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self.type, + "relationships": self.relationships + } + } + return payload + class CreateVerifiedPaymentRequest(CreatePaymentBaseRequest): def __init__(self, amount: int, description: str, plaid_processor_token: str, relationships: Dict[str, Relationship], counterparty_name: Optional[str], verify_counterparty_balance: Optional[bool], @@ -213,8 +356,60 @@ def to_json_api(self) -> Dict: payload["data"]["attributes"]["counterparty"] = self.counterparty return payload + +class CreatePushToCardPaymentRequest(CreatePaymentBaseRequest): + def __init__(self, amount: int, description: str, configuration: dict, + relationships: Dict[str, Relationship], + idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None): + super().__init__(amount, description, relationships, idempotency_key, tags, None, "pushToCardPayment", False, configuration) + + +class CreateCheckPaymentRequest(UnitRequest): + def __init__( + self, + description: str, + amount: int, + counterparty: CheckPaymentCounterparty, + idempotency_key: str, + relationships: Dict[str, Relationship], + memo: Optional[str] = None, + send_date: Optional[str] = None, + tags: Optional[Dict[str, str]] = None, + ): + self.description = description + self.amount = amount + self.send_date = send_date + self.counterparty = counterparty + self.memo = memo + self.idempotency_key = idempotency_key + self.tags = tags + self.relationships = relationships + + def to_json_api(self) -> Dict: + return super().to_payload("checkPayment", self.relationships) + + +class CreateCheckStopPaymentRequest(UnitRequest): + def __init__( + self, + check_number: str, + relationships: Dict[str, Relationship], + amount: Optional[int] = None, + tags: Optional[Dict[str, str]] = None, + idempotency_key: Optional[str] = None + ): + self.amount = amount + self.check_number = check_number + self.tags = tags + self.relationships = relationships + + def to_json_api(self) -> Dict: + return super().to_payload("checkStopPayment", self.relationships) + + CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, - CreateBookPaymentRequest, CreateWirePaymentRequest] + CreateBookPaymentRequest, CreateWirePaymentRequest, CreatePushToCardPaymentRequest, + CreateCheckPaymentRequest, CreateCheckStopPaymentRequest] class PatchAchPaymentRequest(object): def __init__(self, payment_id: str, tags: Dict[str, str]): @@ -256,7 +451,27 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -PatchPaymentRequest = Union[PatchAchPaymentRequest, PatchBookPaymentRequest] +class PatchCheckPaymentRequest(object): + def __init__(self, payment_id: str, tags: Dict[str, str]): + self.payment_id = payment_id + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "checkPayment", + "attributes": { + "tags": self.tags + } + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + +PatchPaymentRequest = Union[PatchAchPaymentRequest, PatchBookPaymentRequest, PatchCheckPaymentRequest] class ListPaymentParams(UnitParams): def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, diff --git a/unit/models/repayment.py b/unit/models/repayment.py new file mode 100644 index 00000000..3554b94b --- /dev/null +++ b/unit/models/repayment.py @@ -0,0 +1,114 @@ +try: + from typing import Optional, Dict, Union, List, Literal +except ImportError: + from typing import Optional, Dict, Union, List + from typing_extensions import Literal + +from unit.models import UnitDTO, extract_attributes, UnitRequest, Relationship, UnitParams +from unit.utils import date_utils + + +class BaseRepayment(UnitDTO): + def __init__(self, _id, _type, attributes, relationships): + self.id = _id + self.type = _type + self.attributes = extract_attributes(["amount", "status", "tags"], attributes) + attrs = {"createdAt": date_utils.to_datetime(attributes["createdAt"]), + "updatedAt": date_utils.to_datetime(attributes["updatedAt"])} + self.attributes.update(attrs) + self.relationships = relationships + + +class BookRepaymentDTO(BaseRepayment): + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BookRepaymentDTO(_id, _type, attributes, relationships) + + +class AchRepaymentDTO(BaseRepayment): + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AchRepaymentDTO(_id, _type, attributes, relationships) + + +RepaymentDTO = Union[BookRepaymentDTO, AchRepaymentDTO] + + +class CreateBookRepaymentRequest(UnitRequest): + def __init__(self, description: str, amount: int, relationships: Dict[str, Relationship], + transaction_summary_override: Optional[str] = None, tags: Optional[Dict[str, str]] = None, + idempotency_key: Optional[str] = None): + self.description = description + self.amount = amount + self.transaction_summary_override = transaction_summary_override + self.tags = tags + self.idempotency_key = idempotency_key + self.relationships = relationships + + def to_json_api(self) -> Dict: + return super().to_payload("bookRepayment", self.relationships) + + +class CreateAchRepaymentRequest(UnitRequest): + def __init__(self, description: str, amount: int, relationships: Dict[str, Relationship], + addenda: Optional[str] = None, tags: Optional[Dict[str, str]] = None, same_day: Optional[bool] = None, + idempotency_key: Optional[str] = None): + self.description = description + self.amount = amount + self.addenda = addenda + self.tags = tags + self.same_day = same_day + self.idempotency_key = idempotency_key + self.relationships = relationships + + def to_json_api(self) -> Dict: + return super().to_payload("achRepayment", self.relationships) + + +CreateRepaymentRequest = Union[CreateBookRepaymentRequest, CreateAchRepaymentRequest] + +RepaymentStatus = Literal["Pending", "PendingReview", "Returned", "Sent", "Rejected"] +RepaymentType = Literal["bookRepayment", "achRepayment"] + + +class ListRepaymentParams(UnitParams): + def __init__( + self, + limit: int = 100, + offset: int = 0, + account_id: Optional[str] = None, + credit_account_id: Optional[str] = None, + customer_id: Optional[str] = None, + status: Optional[List[RepaymentStatus]] = None, + _type: Optional[List[str]] = None, + ): + self.limit = limit + self.offset = offset + self.account_id = account_id + self.credit_account_id = credit_account_id + self.customer_id = customer_id + self.status = status + self.type = _type + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + + if self.account_id: + parameters["filter[accountId]"] = self.account_id + + if self.credit_account_id: + parameters["filter[creditAccountId]"] = self.credit_account_id + + if self.customer_id: + parameters["filter[customerId]"] = self.customer_id + + if self.status: + for idx, status_filter in enumerate(self.status): + parameters[f"filter[status][{idx}]"] = status_filter + + if self.type: + for idx, type_filter in enumerate(self.type): + parameters[f"filter[type][{idx}]"] = type_filter + + return parameters + diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index ecd9eb2c..6c972246 100644 --- a/unit/models/returnAch.py +++ b/unit/models/returnAch.py @@ -2,10 +2,10 @@ from typing import Literal from unit.models import * -AchReturnReason = Literal["Unauthorized"] +AchReturnReason = Literal["InsufficientFunds", "Unauthorized", "UncollectedFunds"] -class ReturnReceivedAchTransactionRequest(object): +class ReturnReceivedAchTransactionRequest(UnitRequest): def __init__(self, transaction_id: str, reason: AchReturnReason, relationships: [Dict[str, Relationship]]): self.transaction_id = transaction_id self.reason = reason diff --git a/unit/models/reward.py b/unit/models/reward.py new file mode 100644 index 00000000..07f1535b --- /dev/null +++ b/unit/models/reward.py @@ -0,0 +1,137 @@ +import json +from typing import Optional, Literal, Dict, List +from datetime import datetime + +from unit.models import Relationship, UnitRequest, UnitParams + + +SORT_ORDERS = Literal["created_at", "-created_at"] +RELATED_RESOURCES = Literal["customer", "account", "transaction"] + + +class RewardDTO(object): + def __init__(self, id: str, amount: int, description: str, status: str, tags: Optional[Dict[str, str]] = None, + relationships: Optional[Dict[str, Relationship]] = None): + self.id = id + self.type = "reward" + self.attributes = {"amount": amount, "description": description, "status": status, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, attributes, relationships): + return RewardDTO(_id, attributes["amount"], attributes["description"], attributes["status"], attributes.get("tags"), relationships) + + +class CreateRewardRequest(UnitRequest): + def __init__( + self, + amount: int, + description: str, + receiving_account_id: str, + rewarded_transaction_id: Optional[str] = None, + funding_account_id: Optional[str] = None, + idempotency_key: Optional[str] = None, + tags: Optional[Dict[str, str]] = None + ): + self.type = "reward" + self.amount = amount + self.description = description + self.rewarded_transaction_id = rewarded_transaction_id + self.receiving_account_id = receiving_account_id + self.funding_account_id = funding_account_id + self.idempotency_key = idempotency_key + self.tags = tags + + self.relationships = { + "receivingAccount": Relationship(_type="depositAccount", _id=self.receiving_account_id) + } + if self.rewarded_transaction_id: + self.relationships["rewardedTransaction"] = Relationship(_type="transaction", _id=self.rewarded_transaction_id) + + if self.funding_account_id: + self.relationships["fundingAccount"] = Relationship(_type="depositAccount", _id=self.funding_account_id) + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self.type, + "attributes": { + "amount": self.amount, + "description": self.description + }, + "relationships": self.relationships + } + } + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class ListRewardsParams(UnitParams): + def __init__( + self, + limit: int = 100, + offset: int = 0, + transaction_id: Optional[str] = None, + rewarded_transaction_id: Optional[str] = None, + receiving_account_id: Optional[str] = None, + customer_id: Optional[str] = None, + card_id: Optional[str] = None, + status: Optional[str] = None, + since: Optional[datetime] = None, + until: Optional[datetime] = None, + sort: Optional[SORT_ORDERS] = None, + include: Optional[List[RELATED_RESOURCES]] = None, + ): + self.limit = limit + self.offset = offset + self.transaction_id = transaction_id + self.rewarded_transaction_id = rewarded_transaction_id + self.receiving_account_id = receiving_account_id + self.customer_id = customer_id + self.card_id = card_id + self.status = status + self.since = since + self.until = until + self.sort = sort + self.include = include + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + + if self.transaction_id: + parameters["filter[transactionId]"] = self.transaction_id + + if self.rewarded_transaction_id: + parameters["filter[rewardedTransactionId]"] = self.rewarded_transaction_id + + if self.receiving_account_id: + parameters["filter[receivingAccountId]"] = self.receiving_account_id + + if self.customer_id: + parameters["filter[customerId]"] = self.customer_id + + if self.card_id: + parameters["filter[cardId]"] = self.card_id + + if self.status: + parameters["filter[status]"] = self.status + + if self.since: + parameters["filter[since]"] = self.since + + if self.unitl: + parameters["filter[until]"] = self.until + + if self.sort: + parameters["sort"] = self.sort + + return parameters diff --git a/unit/models/statement.py b/unit/models/statement.py index 0b519ade..590e2604 100644 --- a/unit/models/statement.py +++ b/unit/models/statement.py @@ -16,7 +16,7 @@ def from_json_api(_id, _type, attributes, relationships): OutputType = Literal["html", "pdf"] -class GetStatementParams(object): +class GetStatementParams(UnitRequest): def __init__(self, statement_id: str, output_type: Optional[OutputType] = "html", language: Optional[str] = "en", customer_id: Optional[str] = None): self.statement_id = statement_id diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 67d40f70..59e2d17e 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -113,8 +113,8 @@ def from_json_api(_id, _type, attributes, relationships): class BookTransactionDTO(BaseTransactionDTO): def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, counterparty: Counterparty, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): + summary: str, counterparty: Counterparty, tags: Optional[Dict[str, str]] = None, + relationships: Optional[Dict[str, Relationship]] = None): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.type = 'bookTransaction' self.attributes["counterparty"] = counterparty @@ -122,21 +122,28 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b @staticmethod def from_json_api(_id, _type, attributes, relationships): return BookTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - Counterparty.from_json_api(attributes["counterparty"]), attributes.get("tags"), relationships) + id=_id, created_at=date_utils.to_datetime(attributes["createdAt"]), direction=attributes["direction"], + amount=attributes["amount"], balance=attributes["balance"], summary=attributes["summary"], + counterparty=Counterparty.from_json_api(attributes["counterparty"]), + tags=attributes.get("tags"), relationships=relationships + ) class PurchaseTransactionDTO(BaseTransactionDTO): def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, card_last_4_digits: str, merchant: Merchant, coordinates: Coordinates, recurring: bool, - interchange: Optional[int], ecommerce: bool, card_present: bool, payment_method: Optional[str], - digital_wallet: Optional[str], card_verification_data, card_network: Optional[str], - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + summary: str, card_last_4_digits: str, merchant: Optional[Merchant], coordinates: Optional[Coordinates], + recurring: bool, ecommerce: bool, card_present: bool, card_verification_data, + interchange: Optional[int] = None, payment_method: Optional[str] = None, + digital_wallet: Optional[str] = None, card_network: Optional[str] = None, + tags: Optional[Dict[str, str]] = None, relationships: Optional[Dict[str, Relationship]] = None, + gross_interchange: Optional[str] = None, cash_withdrawal_amount: Optional[int] = None, + currency_conversion: Optional[CurrencyConversion] = None, + rich_merchant_data: Optional[RichMerchantData] = None, last_4_digits: str = None, ): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.type = 'purchaseTransaction' self.attributes["cardLast4Digits"] = card_last_4_digits - self.attributes["merchant"] = merchant + if merchant: + self.attributes["merchant"] = merchant self.attributes["coordinates"] = coordinates self.attributes["recurring"] = recurring self.attributes["interchange"] = interchange @@ -146,17 +153,33 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b self.attributes["digitalWallet"] = digital_wallet self.attributes["cardVerificationData"] = card_verification_data self.attributes["cardNetwork"] = card_network + self.attributes["grossInterchange"] = gross_interchange + self.attributes["cashWithdrawalAmount"] = cash_withdrawal_amount + self.attributes["currencyConversion"] = currency_conversion + self.attributes["richMerchantData"] = rich_merchant_data + + # Unit incorrectly returns last4Digits for simulation responses + if last_4_digits: + self.attributes["last4Digits"] = last_4_digits @staticmethod def from_json_api(_id, _type, attributes, relationships): + # Purchase simulations do not return the merchant attribute + simulation_merchant = dict( + name=attributes.get("merchantName", None), + type=attributes.get("merchantType", None), + location=attributes.get("merchantLocation", None), + ) return PurchaseTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], attributes["cardLast4Digits"], - Merchant.from_json_api(attributes["merchant"]), Coordinates.from_json_api(attributes["coordinates"]), - attributes["recurring"], attributes.get("interchange"), attributes.get("ecommerce"), - attributes.get("cardPresent"), attributes.get("paymentMethod"), attributes.get("digitalWallet"), - attributes.get("cardVerificationData"), attributes.get("cardNetwork"), attributes.get("tags"), - relationships) + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], attributes["amount"], + attributes["balance"], attributes.get("summary"), attributes.get("cardLast4Digits", None), + Merchant.from_json_api(attributes.get("merchant") or simulation_merchant), Coordinates.from_json_api(attributes.get("coordinates")), + attributes["recurring"], attributes.get("ecommerce"), attributes.get("cardPresent"), + attributes.get("cardVerificationData"), attributes.get("interchange"), attributes.get("paymentMethod"), + attributes.get("digitalWallet"), attributes.get("cardNetwork"), attributes.get("tags"), + relationships, attributes.get("grossInterchange"), attributes.get("cashWithdrawalAmount"), + CurrencyConversion.from_json_api(attributes.get("currencyConversion")), + RichMerchantData.from_json_api(attributes.get("richMerchantData")), attributes.get("last4Digits", None)) class AtmTransactionDTO(BaseTransactionDTO): @@ -197,14 +220,15 @@ def from_json_api(_id, _type, attributes, relationships): class CardTransactionDTO(BaseTransactionDTO): def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, card_last_4_digits: str, merchant: Merchant, recurring: Optional[bool], + summary: str, card_last_4_digits: str, merchant: Optional[Merchant], recurring: Optional[bool], interchange: Optional[int], payment_method: Optional[str], digital_wallet: Optional[str], card_verification_data: Optional[Dict], card_network: Optional[str], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.type = 'cardTransaction' self.attributes["cardLast4Digits"] = card_last_4_digits - self.attributes["merchant"] = merchant + if merchant: + self.attributes["merchant"] = merchant self.attributes["recurring"] = recurring self.attributes["interchange"] = interchange self.attributes["paymentMethod"] = payment_method @@ -217,7 +241,7 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b def from_json_api(_id, _type, attributes, relationships): return CardTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], attributes["amount"], attributes["balance"], attributes["summary"], - attributes["cardLast4Digits"], Merchant.from_json_api(attributes["merchant"]), + attributes["cardLast4Digits"], Merchant.from_json_api(attributes.get("merchant")), attributes.get("recurring"), attributes.get("interchange"), attributes.get("paymentMethod"), attributes.get("digitalWallet"), attributes.get("cardVerificationData"), attributes.get("cardNetwork"), @@ -361,6 +385,35 @@ def from_json_api(_id, _type, attributes, relationships): attributes["amount"], attributes["balance"], attributes["summary"], attributes["reason"], attributes.get("tags"), relationships) + +class CheckPaymentTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'checkPaymentTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes.get("tags"), relationships) + + +class ReturnedCheckPaymentTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, + return_reason: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'returnedCheckPaymentTransaction' + self.attributes["returnReason"] = return_reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReturnedCheckPaymentTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes["returnReason"], attributes.get("tags"), relationships) + + class PaymentAdvanceTransactionDTO(BaseTransactionDTO): def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, reason: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): @@ -387,13 +440,50 @@ def from_json_api(_id, _type, attributes, relationships): attributes["amount"], attributes["balance"], attributes["summary"], attributes.get("tags"), relationships) +class AccountLowBalanceClosureTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, amount: int, direction: str, + balance: int, summary: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'accountLowBalanceClosureTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AccountLowBalanceClosureTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), + attributes["amount"], attributes["direction"], attributes["balance"], attributes["summary"], attributes.get("tags"), relationships) + +class NegativeBalanceCoverageTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, amount: int, direction: str, + balance: int, summary: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'negativeBalanceCoverageTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return NegativeBalanceCoverageTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["amount"], attributes["direction"], attributes["balance"], attributes["summary"], attributes.get("tags"), relationships) + +class WriteOffTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, amount: int, direction: str, + balance: int, summary: str, tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'writeOffTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return WriteOffTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["amount"], attributes["direction"], attributes["balance"], attributes["summary"], attributes.get("tags"), relationships) + + TransactionDTO = Union[OriginatedAchTransactionDTO, ReceivedAchTransactionDTO, ReturnedAchTransactionDTO, ReturnedReceivedAchTransactionDTO, DishonoredAchTransactionDTO, BookTransactionDTO, PurchaseTransactionDTO, AtmTransactionDTO, FeeTransactionDTO, CardTransactionDTO, CardReversalTransactionDTO, WireTransactionDTO, ReleaseTransactionDTO, AdjustmentTransactionDTO, InterestTransactionDTO, DisputeTransactionDTO, CheckDepositTransactionDTO, - ReturnedCheckDepositTransactionDTO, PaymentAdvanceTransactionDTO, - RepaidPaymentAdvanceTransactionDTO] + ReturnedCheckDepositTransactionDTO, CheckPaymentTransactionDTO, + ReturnedCheckPaymentTransactionDTO, PaymentAdvanceTransactionDTO, + RepaidPaymentAdvanceTransactionDTO, AccountLowBalanceClosureTransactionDTO, NegativeBalanceCoverageTransactionDTO, + WriteOffTransactionDTO] class PatchTransactionRequest(BaseTransactionDTO, UnitRequest): @@ -461,4 +551,118 @@ def to_dict(self) -> Dict: parameters["sort"] = self.sort if self.include: parameters["include"] = self.include - return parameters \ No newline at end of file + return parameters + + +class SimulatePurchaseTransaction(UnitRequest): + def __init__( + self, + amount: int, + card_id: str, + last_4_Digits: str, + deposit_account_id: str, + merchantName: str, + merchantType: str, + merchantLocation: str, + direction: str = "Debit", + authorization_id: str = None, + ): + self.authorization_id = authorization_id + self.last_4_Digits = last_4_Digits + self.deposit_account_id = deposit_account_id + self.direction = direction + self.amount = amount + self.card_id = card_id + self.merchantName = merchantName + self.merchantType = merchantType + self.merchantLocation = merchantLocation + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "purchaseTransaction", + "attributes": { + "amount": self.amount, + "direction": self.direction, + "last4Digits": self.last_4_Digits, + "merchantName": self.merchantName, + "merchantType": self.merchantType, + "merchantLocation": self.merchantLocation, + "recurring": False + }, + "relationships": { + "account": { + "data": { + "type": "depositAccount", + "id": self.deposit_account_id + } + } + } + + } + } + + if self.authorization_id: + payload["data"]["relationships"]["authorization"] = { + "data": { + "type": "authorization", + "id": self.authorization_id + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class SimulateCardTransaction(UnitRequest): + def __init__( + self, + amount: int, + card_id: str, + card_last_4_Digits: str, + deposit_account: str, + merchantName: str, + merchantType: str, + merchantLocation: str, + direction: str = "Debit", + ): + self.card_last_4_Digits = card_last_4_Digits + self.deposit_account = deposit_account + self.direction = direction + self.amount = amount + self.card_id = card_id + self.merchantName = merchantName + self.merchantType = merchantType + self.merchantLocation = merchantLocation + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "cardTransaction", + "attributes": { + "amount": self.amount, + "direction": self.direction, + "cardLast4Digits": self.card_last_4_Digits, + "merchantName": "The Home Depot", + "merchantType": self.merchantType, + "merchantLocation": self.merchantLocation, + "recurring": False + }, + "relationships": { + "account": { + "data": { + "type": "depositAccount", + "id": self.deposit_account + } + }, + } + + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) \ No newline at end of file diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO new file mode 100644 index 00000000..f8e13e2e --- /dev/null +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -0,0 +1,19 @@ +Metadata-Version: 2.1 +Name: unit-python-sdk +Version: 0.10.5 +Summary: This library provides a python wrapper to http://unit.co API. See https://docs.unit.co/ +Home-page: https://github.com/unit-finance/unit-python-sdk +Download-URL: https://github.com/unit-finance/unit-python-sdk.git +Author: unit.co +Author-email: dev@unit.co +License: Mozilla Public License 2.0 +Keywords: unit,finance,banking,banking-as-a-service,API,SDK +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Build Tools +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +License-File: LICENSE +Requires-Dist: requests diff --git a/unit_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt new file mode 100644 index 00000000..f4f690c1 --- /dev/null +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -0,0 +1,75 @@ +LICENSE +README.md +setup.cfg +setup.py +unit/__init__.py +unit/api/__init__.py +unit/api/account_end_of_day_resource.py +unit/api/account_resource.py +unit/api/ach_resource.py +unit/api/api_token_resource.py +unit/api/applicationForm_resource.py +unit/api/application_resource.py +unit/api/atmLocation_resource.py +unit/api/authorization_request_resource.py +unit/api/authorization_resource.py +unit/api/base_resource.py +unit/api/batch_release_resource.py +unit/api/bill_pay_resource.py +unit/api/card_resource.py +unit/api/check_deposit_resource.py +unit/api/check_payment_resource.py +unit/api/check_stop_payment_resource.py +unit/api/counterparty_resource.py +unit/api/customerToken_resource.py +unit/api/customer_resource.py +unit/api/dispute_resource.py +unit/api/event_resource.py +unit/api/fee_resource.py +unit/api/institution_resource.py +unit/api/payment_resource.py +unit/api/received_payment_resource.py +unit/api/repayment_resource.py +unit/api/returnAch_resource.py +unit/api/reward_resource.py +unit/api/statement_resource.py +unit/api/transaction_resource.py +unit/api/webhook_resource.py +unit/models/__init__.py +unit/models/account.py +unit/models/account_end_of_day.py +unit/models/api_token.py +unit/models/application.py +unit/models/applicationForm.py +unit/models/atm_location.py +unit/models/authorization.py +unit/models/authorization_request.py +unit/models/batch_release.py +unit/models/benificial_owner.py +unit/models/bill_pay.py +unit/models/card.py +unit/models/check_deposit.py +unit/models/check_payment.py +unit/models/check_stop_payment.py +unit/models/codecs.py +unit/models/counterparty.py +unit/models/customer.py +unit/models/customerToken.py +unit/models/dispute.py +unit/models/event.py +unit/models/fee.py +unit/models/institution.py +unit/models/payment.py +unit/models/repayment.py +unit/models/returnAch.py +unit/models/reward.py +unit/models/statement.py +unit/models/transaction.py +unit/models/webhook.py +unit/utils/__init__.py +unit/utils/date_utils.py +unit_python_sdk.egg-info/PKG-INFO +unit_python_sdk.egg-info/SOURCES.txt +unit_python_sdk.egg-info/dependency_links.txt +unit_python_sdk.egg-info/requires.txt +unit_python_sdk.egg-info/top_level.txt \ No newline at end of file diff --git a/unit_python_sdk.egg-info/dependency_links.txt b/unit_python_sdk.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/unit_python_sdk.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/unit_python_sdk.egg-info/requires.txt b/unit_python_sdk.egg-info/requires.txt new file mode 100644 index 00000000..f2293605 --- /dev/null +++ b/unit_python_sdk.egg-info/requires.txt @@ -0,0 +1 @@ +requests diff --git a/unit_python_sdk.egg-info/top_level.txt b/unit_python_sdk.egg-info/top_level.txt new file mode 100644 index 00000000..47b23a20 --- /dev/null +++ b/unit_python_sdk.egg-info/top_level.txt @@ -0,0 +1 @@ +unit