From a9dd9dc7606172a9a54593d93d2f8d29c127b1c3 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 2 Feb 2022 16:46:00 -0800 Subject: [PATCH 001/181] bug patches --- unit/models/__init__.py | 2 +- unit/models/application.py | 36 +++++++++++++++++++++++++++++------- unit/models/codecs.py | 2 ++ unit/models/customer.py | 2 +- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 18404c62..70246cf5 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -176,7 +176,7 @@ def __init__(self, full_name: FullName, email: str, phone: Phone): self.phone = phone @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"))) diff --git a/unit/models/application.py b/unit/models/application.py index daa2bfa0..bc395f00 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -67,7 +67,8 @@ def from_json_api(_id, _type, attributes, relationships): 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, ssn = None): + ip: str = None, ein: str = None, dba: str = None, sole_proprietorship: bool = None, ssn = None, + tags: Optional[Dict[str, str]] = None): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -78,6 +79,7 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, e self.dba = dba self.sole_proprietorship = sole_proprietorship self.ssn = ssn + self.tags = tags def to_json_api(self) -> Dict: payload = { @@ -108,6 +110,9 @@ def to_json_api(self) -> Dict: if self.ssn: payload["data"]["attributes"]["ssn"] = self.ssn + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + return payload def __repr__(self): @@ -115,9 +120,22 @@ 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, + ): self.name = name self.address = address self.phone = phone @@ -130,8 +148,9 @@ def __init__(self, name: str, address: Address, phone: Phone, state_of_incorpora self.dba = dba self.ip = ip self.website = website + self.tags = tags - def to_json_api(self) -> Dict: + def to_json_api(self) -> dict: payload = { "data": { "type": "businessApplication", @@ -144,14 +163,17 @@ def to_json_api(self) -> Dict: "contact": self.contact, "officer": self.officer, "beneficialOwners": self.beneficial_owners, - "entityType": self.entity_type - } + "entityType": self.entity_type, + }, } } 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 diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 904a5078..c8778aae 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -312,6 +312,8 @@ 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): + return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} 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} diff --git a/unit/models/customer.py b/unit/models/customer.py index bd82690a..b540d042 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -43,7 +43,7 @@ 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"]], + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("dba"), attributes.get("tags"), relationships) CustomerDTO = Union[IndividualCustomerDTO, BusinessCustomerDTO] From a07b7a4b486bc7fa11c4b0b1cd990025c3ae7bed Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Feb 2022 14:01:10 -0800 Subject: [PATCH 002/181] fixed inheritence and changed pdf response to content --- unit/api/statement_resource.py | 2 +- unit/models/statement.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/api/statement_resource.py b/unit/api/statement_resource.py index 212a590f..1ba5342c 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()) diff --git a/unit/models/statement.py b/unit/models/statement.py index 90e40382..03043188 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 From ef2c0d6eaf19036e4ce4176ff1aefd74d08f5af3 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 14 Feb 2022 14:38:50 -0800 Subject: [PATCH 003/181] add auth user to sol prop --- unit/models/customer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unit/models/customer.py b/unit/models/customer.py index b540d042..39580df2 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -51,12 +51,14 @@ def from_json_api(_id, _type, attributes, relationships): 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 +81,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 From 8994d1323ddf5bb0e6e6c7bf4208540378d11f27 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 16 Feb 2022 20:20:23 -0800 Subject: [PATCH 004/181] added payment rejected event --- unit/models/event.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 74b213ac..c891a801 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -304,6 +304,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, previous_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["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + 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]]): @@ -348,5 +360,6 @@ def from_json_api(_id, _type, attributes, relationships): AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, - PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent] + PaymentReturnedEvent, PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, + AccountReopenedEvent] From d5c15b29fb530302539590c50ffbc60adf37f630 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 18 Feb 2022 10:35:34 -0800 Subject: [PATCH 005/181] added auth users to individual customer dto --- unit/models/__init__.py | 3 +++ unit/models/customer.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 70246cf5..0d5a3a1e 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -79,6 +79,9 @@ 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")) diff --git a/unit/models/customer.py b/unit/models/customer.py index 39580df2..4af73886 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -5,12 +5,13 @@ 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]]): 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} self.relationships = relationships @staticmethod @@ -20,7 +21,7 @@ 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 ) From a20ad0530979ced0c9e4b778fa9f6c6d09156305 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 23 Feb 2022 15:25:19 -0800 Subject: [PATCH 006/181] added nsf to ach return reasons --- unit/models/returnAch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index ecd9eb2c..7f22934d 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"] -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 From 4bf5ad3cc25881182445d56ac5b7c39e73242a46 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 2 Mar 2022 17:15:58 -0800 Subject: [PATCH 007/181] fixed transaction dto --- unit/models/transaction.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 48e3bc22..41c93e2e 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -57,7 +57,6 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.type = 'returnedAchTransaction' - self.attributes["addenda"] = addenda self.attributes["companyName"] = company_name self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number self.attributes["reason"] = reason @@ -113,7 +112,7 @@ 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, description: str, addenda: Optional[str], counterparty: Counterparty, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + tags: Optional[Dict[str, str]] = {}, relationships: Optional[Dict[str, Relationship]] = {}): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.description = description self.type = 'bookTransaction' @@ -122,9 +121,12 @@ 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"], + description=attributes["summary"], addenda=None, + counterparty=Counterparty.from_json_api(attributes["counterparty"]), + tags=attributes.get("tags"), relationships=relationships + ) class PurchaseTransactionDTO(BaseTransactionDTO): From 28d000443d40825919a75e565724b8e0f8bc38ea Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 9 Mar 2022 17:40:29 -0800 Subject: [PATCH 008/181] update --- unit/api/transaction_resource.py | 5 +++-- unit/models/returnAch.py | 2 +- unit/models/transaction.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/unit/api/transaction_resource.py b/unit/api/transaction_resource.py index da9f3587..24df54f1 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) -> Union[UnitResponse[TransactionDTO], UnitError]: - response = super().get(f"{self.resource}/{transaction_id}") + def get(self, transaction_id: str, account_id: str) -> Union[UnitResponse[TransactionDTO], UnitError]: + params = {"filter[accountId]": account_id} + response = super().get(f"{self.resource}/{transaction_id}", params) if response.status_code == 200: data = response.json().get("data") included = response.json().get("included") diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index 7f22934d..6c972246 100644 --- a/unit/models/returnAch.py +++ b/unit/models/returnAch.py @@ -2,7 +2,7 @@ from typing import Literal from unit.models import * -AchReturnReason = Literal["InsufficientFunds", "Unauthorized"] +AchReturnReason = Literal["InsufficientFunds", "Unauthorized", "UncollectedFunds"] class ReturnReceivedAchTransactionRequest(UnitRequest): diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 41c93e2e..eca55760 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -231,7 +231,7 @@ def from_json_api(_id, _type, attributes, relationships): return WireTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], attributes["amount"], attributes["balance"], attributes["summary"], Counterparty.from_json_api(attributes["counterparty"]), attributes["description"], - attributes["senderReference"], attributes["referenceForBeneficiary"], + attributes.get("senderReference"), attributes.get("referenceForBeneficiary"), attributes.get("tags"), relationships) From eabb2433cca3b0539a05fc2b0d89109dbe0510b2 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 2 Feb 2022 16:46:00 -0800 Subject: [PATCH 009/181] bug patches --- unit/models/__init__.py | 2 +- unit/models/application.py | 33 +++++++++++++++++++++++++-------- unit/models/codecs.py | 2 ++ unit/models/customer.py | 2 +- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 4e367816..6cf1a4bf 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -176,7 +176,7 @@ def __init__(self, full_name: FullName, email: str, phone: Phone): self.phone = phone @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"))) diff --git a/unit/models/application.py b/unit/models/application.py index 530b6d4b..60f5a5c9 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -69,7 +69,7 @@ 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, + device_fingerprints: Optional[List[DeviceFingerprint]] = None, idempotency_key: str = None, tags: Optional[Dict[str, str]] = None): self.full_name = full_name self.date_of_birth = date_of_birth @@ -129,7 +129,7 @@ def to_json_api(self) -> Dict: payload["data"]["attributes"]["deviceFingerprints"] = [e.to_json_api() for e in self.device_fingerprints] if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + payload["data"]["attributes"]["tags"] = self.tags return payload @@ -138,9 +138,22 @@ 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, + ): self.name = name self.address = address self.phone = phone @@ -153,8 +166,9 @@ def __init__(self, name: str, address: Address, phone: Phone, state_of_incorpora self.dba = dba self.ip = ip self.website = website + self.tags = tags - def to_json_api(self) -> Dict: + def to_json_api(self) -> dict: payload = { "data": { "type": "businessApplication", @@ -167,14 +181,17 @@ def to_json_api(self) -> Dict: "contact": self.contact, "officer": self.officer, "beneficialOwners": self.beneficial_owners, - "entityType": self.entity_type - } + "entityType": self.entity_type, + }, } } 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 diff --git a/unit/models/codecs.py b/unit/models/codecs.py index aa70d2fd..93c50803 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -314,6 +314,8 @@ 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): + return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} 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} diff --git a/unit/models/customer.py b/unit/models/customer.py index 4d9e7e34..f87889d8 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -43,7 +43,7 @@ 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"]], + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("dba"), attributes.get("tags"), relationships) CustomerDTO = Union[IndividualCustomerDTO, BusinessCustomerDTO] From 4a6771add8397b3071be591c02630de498fc742c Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Feb 2022 14:01:10 -0800 Subject: [PATCH 010/181] fixed inheritence and changed pdf response to content --- unit/api/statement_resource.py | 2 +- unit/models/statement.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/api/statement_resource.py b/unit/api/statement_resource.py index 8d0f47b1..be711e1f 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()) 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 From 86914a33ab06b6310e152394e7f9a3de4da9e4b7 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 14 Feb 2022 14:38:50 -0800 Subject: [PATCH 011/181] add auth user to sol prop --- unit/models/customer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unit/models/customer.py b/unit/models/customer.py index f87889d8..0868ef9c 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -51,12 +51,14 @@ def from_json_api(_id, _type, attributes, relationships): 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 +81,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 From e1293d715b04ff5ffcc2ee6633c49284a89ccc9d Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 16 Feb 2022 20:20:23 -0800 Subject: [PATCH 012/181] added payment rejected event --- unit/models/event.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index eaa775d0..c36eb79e 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -304,6 +304,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, previous_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["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + 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]]): @@ -348,7 +360,8 @@ def from_json_api(_id, _type, attributes, relationships): AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, - PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent] + PaymentReturnedEvent, PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, + AccountReopenedEvent] class ListEventParams(UnitParams): From addd9bdd0aef29cca35e40bc469fd9f6d75c8e11 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 18 Feb 2022 10:35:34 -0800 Subject: [PATCH 013/181] added auth users to individual customer dto --- unit/models/__init__.py | 3 +++ unit/models/customer.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 6cf1a4bf..1bf9bf04 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -79,6 +79,9 @@ 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")) diff --git a/unit/models/customer.py b/unit/models/customer.py index 0868ef9c..83ebb29b 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -5,12 +5,13 @@ 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]]): 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} self.relationships = relationships @staticmethod @@ -20,7 +21,7 @@ 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 ) From 3652df24312a2f1105a40f151aa4c227389e7b9b Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 23 Feb 2022 15:25:19 -0800 Subject: [PATCH 014/181] added nsf to ach return reasons --- unit/models/returnAch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index ecd9eb2c..7f22934d 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"] -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 From 16490932e89c95a14b17fdc4d2588c2e48dd407d Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 2 Mar 2022 17:15:58 -0800 Subject: [PATCH 015/181] fixed transaction dto --- unit/models/transaction.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index daafb9f0..973e4699 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -57,7 +57,6 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.type = 'returnedAchTransaction' - self.attributes["addenda"] = addenda self.attributes["companyName"] = company_name self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number self.attributes["reason"] = reason @@ -113,7 +112,7 @@ 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, description: str, addenda: Optional[str], counterparty: Counterparty, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + tags: Optional[Dict[str, str]] = {}, relationships: Optional[Dict[str, Relationship]] = {}): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.description = description self.type = 'bookTransaction' @@ -122,9 +121,12 @@ 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"], + description=attributes["summary"], addenda=None, + counterparty=Counterparty.from_json_api(attributes["counterparty"]), + tags=attributes.get("tags"), relationships=relationships + ) class PurchaseTransactionDTO(BaseTransactionDTO): From 6d1dd0892458f0761476b280fe5b64fb9f39f8f8 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 9 Mar 2022 17:40:29 -0800 Subject: [PATCH 016/181] update --- unit/api/transaction_resource.py | 5 +++-- unit/models/returnAch.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/unit/api/transaction_resource.py b/unit/api/transaction_resource.py index 2ab9d369..abcb8e38 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") diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index 7f22934d..6c972246 100644 --- a/unit/models/returnAch.py +++ b/unit/models/returnAch.py @@ -2,7 +2,7 @@ from typing import Literal from unit.models import * -AchReturnReason = Literal["InsufficientFunds", "Unauthorized"] +AchReturnReason = Literal["InsufficientFunds", "Unauthorized", "UncollectedFunds"] class ReturnReceivedAchTransactionRequest(UnitRequest): From a82b7838a5538b6e35f5e9be22dc6e8d31bcc639 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Mar 2022 12:27:20 -0800 Subject: [PATCH 017/181] added coded --- unit/models/codecs.py | 3 +++ unit/models/event.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 93c50803..a5d6be4d 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -193,6 +193,9 @@ "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), diff --git a/unit/models/event.py b/unit/models/event.py index c36eb79e..2fb0b58e 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -267,6 +267,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 PaymentClearingEvent(_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.created' + 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]], From 517fb306c16a8624c2c1283b9e4818d6ebd04796 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Mar 2022 14:19:59 -0800 Subject: [PATCH 018/181] added codecs --- unit/models/codecs.py | 6 ++++++ unit/models/event.py | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index a5d6be4d..78d11d8c 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -202,6 +202,9 @@ "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), @@ -211,6 +214,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), diff --git a/unit/models/event.py b/unit/models/event.py index 2fb0b58e..54f35824 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -242,6 +242,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 CustomerCreatedEvent(_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]]): @@ -328,15 +341,15 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("tags"), relationships) class PaymentRejectedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + 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["previousStatus"] = previous_status + self.attributes["reason"] = previous_status @staticmethod def from_json_api(_id, _type, attributes, relationships): - return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["reason"], attributes.get("tags"), relationships) class StatementsCreatedEvent(BaseEvent): From 2a8cfd194b00d962d141204b6e5e02793719806a Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Mar 2022 14:41:30 -0800 Subject: [PATCH 019/181] coded update --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 54f35824..34fc444f 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -345,7 +345,7 @@ def __init__(self, id: str, created_at: datetime, reason: str, tags: Optional[Di relationships: Optional[Dict[str, Relationship]]): BaseEvent.__init__(self, id, created_at, tags, relationships) self.type = 'payment.rejected' - self.attributes["reason"] = previous_status + self.attributes["reason"] = reason @staticmethod def from_json_api(_id, _type, attributes, relationships): From 4b89d1314d5fa280db6d06b415f62218b48a98c2 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 18 Mar 2022 14:02:38 -0700 Subject: [PATCH 020/181] adds support for /sandbox/applications/application_id/approve --- unit/api/application_resource.py | 18 ++++++++++++++++++ unit/models/application.py | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 683dfff6..9e4b7570 100644 --- a/unit/api/application_resource.py +++ b/unit/api/application_resource.py @@ -61,3 +61,21 @@ def upload(self, request: UploadDocumentRequest): return UnitResponse[ApplicationDocumentDTO](DtoDecoder.decode(data), None) else: return UnitError.from_json_api(response.json()) + + def approve_sb(self, request: ApproveApplicationSB): + 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()) \ No newline at end of file diff --git a/unit/models/application.py b/unit/models/application.py index 60f5a5c9..7abb33ef 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -258,3 +258,21 @@ def to_dict(self) -> Dict: if self.sort: parameters["sort"] = self.sort return parameters + +class ApproveApplicationSB(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()) From e3b8a836dfa80992040d24498081fef66d101f4b Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 18 Mar 2022 14:16:45 -0700 Subject: [PATCH 021/181] ApproveApplicationSBRequest --- unit/api/application_resource.py | 2 +- unit/models/application.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 9e4b7570..29051e0f 100644 --- a/unit/api/application_resource.py +++ b/unit/api/application_resource.py @@ -62,7 +62,7 @@ def upload(self, request: UploadDocumentRequest): else: return UnitError.from_json_api(response.json()) - def approve_sb(self, request: ApproveApplicationSB): + def approve_sb(self, request: ApproveApplicationSBRequest): url = f"sandbox/{self.resource}/{request.application_id}/approve" payload = request.to_json_api() diff --git a/unit/models/application.py b/unit/models/application.py index 7abb33ef..98744681 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -259,7 +259,7 @@ def to_dict(self) -> Dict: parameters["sort"] = self.sort return parameters -class ApproveApplicationSB(UnitRequest): +class ApproveApplicationSBRequest(UnitRequest): def __init__(self, application_id: str): self.application_id = application_id From e4fd4c3ec70197bc432123d22bdb9f50881d1fe4 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 22 Mar 2022 18:22:42 -0700 Subject: [PATCH 022/181] update to authorization requests --- unit/models/authorization_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unit/models/authorization_request.py b/unit/models/authorization_request.py index 6eed39a4..f654701d 100644 --- a/unit/models/authorization_request.py +++ b/unit/models/authorization_request.py @@ -34,7 +34,7 @@ def from_json_api(_id, _type, attributes, relationships): 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 From 37dc1742d300140e3431d1730d1bf2ee22fc27f5 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 24 Mar 2022 15:20:36 -0700 Subject: [PATCH 023/181] card dto update --- unit/models/card.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index 5e7b7699..1919acca 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -70,15 +70,15 @@ 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, 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, "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( @@ -93,7 +93,7 @@ def from_json_api(_id, _type, attributes, relationships): Card = Union[IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO, BusinessVirtualDebitCardDTO] -class CreateIndividualDebitCard(object): +class CreateIndividualDebitCard(UnitRequest): 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): @@ -129,7 +129,7 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessDebitCard(object): +class CreateBusinessDebitCard(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], @@ -190,7 +190,7 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateIndividualVirtualDebitCard(object): +class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None): self.idempotency_key = idempotency_key @@ -218,11 +218,11 @@ def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessVirtualDebitCard(object): +class CreateBusinessVirtualDebitCard(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]]): + status: CardStatus, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, + idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, + relationships: Optional[Dict[str, Relationship]] = None): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -233,8 +233,8 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.passport = passport self.nationality = nationality self.idempotency_key = idempotency_key - self.tags = tags - self.relationships = relationships + self.tags = tags or {} + self.relationships = relationships or {} def to_json_api(self) -> Dict: payload = { From d047997af805f7196ac492991d4b55c2854f45b5 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 1 Apr 2022 09:14:57 -0700 Subject: [PATCH 024/181] fix bug --- unit/models/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/card.py b/unit/models/card.py index 1919acca..baa74b69 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -83,7 +83,7 @@ def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration 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"], 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 From 6d99a252d3c01d4f30f96d8b1531a697fc79c6c6 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 6 Apr 2022 15:52:41 +0100 Subject: [PATCH 025/181] added authorization canceled event --- unit/models/event.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 34fc444f..b4655d19 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -73,20 +73,38 @@ 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 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: Dict[str, str], + 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"], 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, @@ -392,7 +410,7 @@ def from_json_api(_id, _type, attributes, relationships): EventDTO = Union[AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, - AuthorizationCreatedEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, + AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, From 09d9aaedde72eb356763ecbdced860ead6f5f40d Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 6 Apr 2022 14:24:53 -0700 Subject: [PATCH 026/181] Adds support for simulating incoming ACH --- unit/api/payment_resource.py | 12 +++++++++++- unit/models/codecs.py | 2 +- unit/models/payment.py | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/unit/api/payment_resource.py b/unit/api/payment_resource.py index 6ef30a43..33c13fda 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): @@ -45,3 +45,13 @@ 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()) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 78d11d8c..bccb4852 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -9,7 +9,7 @@ from unit.models.card import IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO,\ BusinessVirtualDebitCardDTO, PinStatusDTO, CardLimitsDTO from unit.models.transaction import * -from unit.models.payment import AchPaymentDTO, BookPaymentDTO, WirePaymentDTO +from unit.models.payment import AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, SimulateIncomingAchPaymentDTO from unit.models.customerToken import CustomerTokenDTO, CustomerVerificationTokenDTO from unit.models.fee import FeeDTO from unit.models.event import * diff --git a/unit/models/payment.py b/unit/models/payment.py index 3e80116d..80c1161c 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -35,6 +35,29 @@ 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: AchStatus, 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 BookPaymentDTO(BasePayment): def __init__(self, id: str, created_at: datetime, status: str, direction: Optional[str], description: str, amount: int, reason: Optional[str], tags: Optional[Dict[str, str]], @@ -140,6 +163,20 @@ 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 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], From 32ca1a7a84319973c1fcb1adf11cc840ed87ee79 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 7 Apr 2022 13:30:44 +0100 Subject: [PATCH 027/181] added limits to card create card request --- unit/models/card.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index baa74b69..d647967f 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -94,11 +94,12 @@ def from_json_api(_id, _type, attributes, relationships): class CreateIndividualDebitCard(UnitRequest): - 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): + def __init__(self, relationships: Dict[str, Relationship], limits: Optional[Cardlimits] = 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 +116,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 @@ -131,23 +135,25 @@ def __repr__(self): class CreateBusinessDebitCard(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]]): + status: CardStatus, limits: Optional[CardLimits] = None, 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, relationships: Optional[Dict[str, Relationship]] = 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.limits = limits self.shipping_address = shipping_address self.ssn = ssn self.passport = passport self.nationality = nationality self.design = design self.idempotency_key = idempotency_key - self.tags = tags - self.relationships = relationships + self.tags = tags or {} + self.relationships = relationships or {} def to_json_api(self) -> Dict: payload = { @@ -167,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.ssn: payload["data"]["attributes"]["ssn"] = self.ssn @@ -192,8 +201,9 @@ def __repr__(self): class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, - tags: Optional[Dict[str, str]] = None): + limits: Optional[CardLimits] = None, tags: Optional[Dict[str, str]] = None): self.idempotency_key = idempotency_key + self.limits = limits self.tags = tags self.relationships = relationships @@ -209,6 +219,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 @@ -220,7 +233,8 @@ def __repr__(self): class CreateBusinessVirtualDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, + status: CardStatus, limits: Optional[CardLimits] = None, ssn: Optional[str] = None, + passport: Optional[str] = None, nationality: Optional[str] = None, idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, relationships: Optional[Dict[str, Relationship]] = None): self.full_name = full_name @@ -231,6 +245,7 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.status = status self.ssn = ssn self.passport = passport + self.limits = limits self.nationality = nationality self.idempotency_key = idempotency_key self.tags = tags or {} @@ -254,6 +269,9 @@ def to_json_api(self) -> Dict: if self.ssn: payload["data"]["attributes"]["ssn"] = self.ssn + if self.limits: + limits["data"]["attributes"]["limits"] = self.limits + if self.passport: payload["data"]["attributes"]["passport"] = self.passport From 8fdcc9ad2236204a1ce51b77e5f774f268615562 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 8 Apr 2022 12:36:14 +0100 Subject: [PATCH 028/181] card limits --- unit/models/card.py | 40 ++++++++++++++++++++++++++++------------ unit/models/codecs.py | 7 +++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index d647967f..fca40cd4 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -94,7 +94,7 @@ def from_json_api(_id, _type, attributes, relationships): class CreateIndividualDebitCard(UnitRequest): - def __init__(self, relationships: Dict[str, Relationship], limits: Optional[Cardlimits] = None, + 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 @@ -135,7 +135,7 @@ def __repr__(self): class CreateBusinessDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLimits] = None, shipping_address: Optional[Address] = None, + status: CardStatus, limits: Optional[CardLevelLimits] = None, 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, relationships: Optional[Dict[str, Relationship]] = None): @@ -201,7 +201,7 @@ def __repr__(self): class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, - limits: Optional[CardLimits] = 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 @@ -233,7 +233,7 @@ def __repr__(self): class CreateBusinessVirtualDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLimits] = None, ssn: Optional[str] = None, + status: CardStatus, limits: Optional[CardLevelLimits] = None, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, relationships: Optional[Dict[str, Relationship]] = None): @@ -293,12 +293,13 @@ def __repr__(self): CreateCardRequest = Union[CreateIndividualDebitCard, CreateBusinessDebitCard, CreateIndividualVirtualDebitCard, CreateBusinessVirtualDebitCard] -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: @@ -312,6 +313,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 @@ -324,12 +328,13 @@ def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessDebitCard(object): +class PatchBusinessDebitCard(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): + limits: CardLevelLimits = None, tags: Optional[Dict[str, str]] = None): self.card_id = card_id self.shipping_address = shipping_address + self.limits = limits self.design = design self.tags = tags @@ -344,6 +349,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.address: payload["data"]["attributes"]["address"] = self.address @@ -364,9 +372,10 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class PatchIndividualVirtualDebitCard(object): - def __init__(self, card_id: str, tags: Optional[Dict[str, str]] = None): +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: @@ -377,6 +386,9 @@ def to_json_api(self) -> Dict: } } + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.tags: payload["data"]["attributes"]["tags"] = self.tags @@ -385,11 +397,12 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessVirtualDebitCard(object): +class PatchBusinessVirtualDebitCard(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, limits: CardLevelLimits = None, tags: Optional[Dict[str, str]] = None): self.card_id = card_id self.address = address + self.limits = limits self.phone = phone self.email = email self.tags = tags @@ -402,6 +415,9 @@ def to_json_api(self) -> Dict: } } + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.address: payload["data"]["attributes"]["address"] = self.address diff --git a/unit/models/codecs.py b/unit/models/codecs.py index bccb4852..752acdc2 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -305,6 +305,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): From b51dc563b73df5e21f77827a96409ff57fa0456c Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 11 Apr 2022 16:47:20 +0100 Subject: [PATCH 029/181] patch card payload --- unit/models/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/card.py b/unit/models/card.py index fca40cd4..993eb6e0 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -270,7 +270,7 @@ def to_json_api(self) -> Dict: payload["data"]["attributes"]["ssn"] = self.ssn if self.limits: - limits["data"]["attributes"]["limits"] = self.limits + payload["data"]["attributes"]["limits"] = self.limits if self.passport: payload["data"]["attributes"]["passport"] = self.passport From ceb8c42805d12520f3fedddb2ba905fe6c74b8d9 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 12 Apr 2022 12:18:45 +0100 Subject: [PATCH 030/181] fix unit bug --- unit/models/card.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index 993eb6e0..09341f5c 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -389,8 +389,7 @@ def to_json_api(self) -> Dict: if self.limits: payload["data"]["attributes"]["limits"] = self.limits - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + payload["data"]["attributes"]["tags"] = self.tags or {} return payload @@ -427,8 +426,7 @@ def to_json_api(self) -> Dict: if self.email: payload["data"]["attributes"]["email"] = self.email - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + payload["data"]["attributes"]["tags"] = self.tags or {} return payload From 7bca682fdbcbbc7492109e778bb0c2ce47d30e5d Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 22 Apr 2022 10:11:34 -0700 Subject: [PATCH 031/181] fix payment rejected type --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index b4655d19..0ea16ce1 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -314,7 +314,7 @@ 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.created' + self.type = 'payment.rejected' self.attributes["status"] = status @staticmethod From 8f90e126869126f081aedae9a378fd3c948a7082 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 22 Apr 2022 10:26:24 -0700 Subject: [PATCH 032/181] PaymentCreated event returning Payment clearing event --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 0ea16ce1..04076185 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -307,7 +307,7 @@ def __init__(self, id: str, created_at: datetime, status: str, tags: Optional[Di @staticmethod def from_json_api(_id, _type, attributes, relationships): - return PaymentClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + return PaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], attributes.get("tags"), relationships) class PaymentRejectedEvent(BaseEvent): From cf574d91edffba17be5a6fda4c3322c1df5971fb Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 2 May 2022 17:39:22 -0700 Subject: [PATCH 033/181] fix benificial owner bug --- unit/models/benificial_owner.py | 26 ++++++++++++++++++++++++++ unit/models/codecs.py | 6 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 unit/models/benificial_owner.py 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/codecs.py b/unit/models/codecs.py index 752acdc2..dc06ad79 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -23,6 +23,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: @@ -249,7 +250,10 @@ "pinStatus": lambda _id, _type, attributes, relationships: PinStatusDTO.from_json_api(attributes), - } + + "beneficialOwner": lambda _id, _type, attributes, relationships: + BenificialOwnerDTO.from_json_api(attributes), +} def split_json_api_single_response(payload: Dict): From 3513c592cd3cba3da6b81613f637f81d328c83ec Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 4 May 2022 17:28:46 -0700 Subject: [PATCH 034/181] Removes deprecated SSN field from card api --- unit/models/card.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index 09341f5c..b368ad20 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -25,7 +25,7 @@ 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, + 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], shipping_address: Optional[Address], design: Optional[str], @@ -33,7 +33,7 @@ def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration self.id = id self.type = "businessDebitCard" 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, "shippingAddress": shipping_address, "design": design} self.relationships = relationships @@ -42,7 +42,7 @@ 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"]), + 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"), @@ -68,14 +68,14 @@ 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] = 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 or {} @@ -83,7 +83,7 @@ def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration def from_json_api(_id, _type, attributes, relationships): return BusinessVirtualDebitCardDTO( _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], - attributes["expirationDate"], attributes.get("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 @@ -233,7 +233,7 @@ def __repr__(self): class CreateBusinessVirtualDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLevelLimits] = None, ssn: Optional[str] = None, + status: CardStatus, limits: Optional[CardLevelLimits] = None, passport: Optional[str] = None, nationality: Optional[str] = None, idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, relationships: Optional[Dict[str, Relationship]] = None): @@ -243,7 +243,6 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.phone = phone self.email = email self.status = status - self.ssn = ssn self.passport = passport self.limits = limits self.nationality = nationality @@ -266,9 +265,6 @@ def to_json_api(self) -> Dict: } } - if self.ssn: - payload["data"]["attributes"]["ssn"] = self.ssn - if self.limits: payload["data"]["attributes"]["limits"] = self.limits From 6dd43fc77d2b175f08da0f925c07a3aa66263f05 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 2 Feb 2022 16:46:00 -0800 Subject: [PATCH 035/181] bug patches --- unit/models/__init__.py | 2 +- unit/models/application.py | 29 +++++++++++++++++++++++------ unit/models/codecs.py | 2 ++ unit/models/customer.py | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index e7fe6e81..b92c301f 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -183,7 +183,7 @@ def __init__(self, full_name: FullName, email: str, phone: Phone): self.phone = phone @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"))) diff --git a/unit/models/application.py b/unit/models/application.py index d8edfec6..cca5a2d1 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -141,9 +141,22 @@ 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, + ): self.name = name self.address = address self.phone = phone @@ -156,8 +169,9 @@ def __init__(self, name: str, address: Address, phone: Phone, state_of_incorpora self.dba = dba self.ip = ip self.website = website + self.tags = tags - def to_json_api(self) -> Dict: + def to_json_api(self) -> dict: payload = { "data": { "type": "businessApplication", @@ -170,14 +184,17 @@ def to_json_api(self) -> Dict: "contact": self.contact, "officer": self.officer, "beneficialOwners": self.beneficial_owners, - "entityType": self.entity_type - } + "entityType": self.entity_type, + }, } } 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 diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 45bd1f6c..12cdce47 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -332,6 +332,8 @@ 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): + return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} 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} diff --git a/unit/models/customer.py b/unit/models/customer.py index 4d9e7e34..f87889d8 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -43,7 +43,7 @@ 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"]], + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("dba"), attributes.get("tags"), relationships) CustomerDTO = Union[IndividualCustomerDTO, BusinessCustomerDTO] From 7604c3a6290acfd3171a8d6ffbc2801f5ed53432 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Feb 2022 14:01:10 -0800 Subject: [PATCH 036/181] fixed inheritence and changed pdf response to content --- unit/api/statement_resource.py | 2 +- unit/models/statement.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/api/statement_resource.py b/unit/api/statement_resource.py index 8d0f47b1..be711e1f 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()) 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 From f82564e2c17c159e2d38ae9151e7af1a9b510bed Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 14 Feb 2022 14:38:50 -0800 Subject: [PATCH 037/181] add auth user to sol prop --- unit/models/customer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unit/models/customer.py b/unit/models/customer.py index f87889d8..0868ef9c 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -51,12 +51,14 @@ def from_json_api(_id, _type, attributes, relationships): 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 +81,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 From 4b6f09c21af95f6d850fda8734a738db4d8dd091 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 16 Feb 2022 20:20:23 -0800 Subject: [PATCH 038/181] added payment rejected event --- unit/models/event.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/unit/models/event.py b/unit/models/event.py index cf922d1d..568e664f 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -304,6 +304,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, previous_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["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + 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]]): From 1f8895de056246c0c02530ae71f9bb755c85c37e Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 18 Feb 2022 10:35:34 -0800 Subject: [PATCH 039/181] added auth users to individual customer dto --- unit/models/__init__.py | 3 +++ unit/models/customer.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index b92c301f..f5589981 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -86,6 +86,9 @@ 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")) diff --git a/unit/models/customer.py b/unit/models/customer.py index 0868ef9c..83ebb29b 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -5,12 +5,13 @@ 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]]): 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} self.relationships = relationships @staticmethod @@ -20,7 +21,7 @@ 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 ) From 86bb4204997788418df954f65928bc514385b5b8 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 23 Feb 2022 15:25:19 -0800 Subject: [PATCH 040/181] added nsf to ach return reasons --- unit/models/returnAch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index ecd9eb2c..7f22934d 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"] -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 From 669d152ce7d1653269fb32c69b2a8aa59eba7fd9 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 2 Mar 2022 17:15:58 -0800 Subject: [PATCH 041/181] fixed transaction dto --- unit/models/transaction.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 67d40f70..b0d7efe6 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,9 +122,11 @@ 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): From b57a1acbd2b5f06fdb47e0d34b28168639885960 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 9 Mar 2022 17:40:29 -0800 Subject: [PATCH 042/181] update --- unit/api/transaction_resource.py | 5 +++-- unit/models/returnAch.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/unit/api/transaction_resource.py b/unit/api/transaction_resource.py index 2ab9d369..abcb8e38 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") diff --git a/unit/models/returnAch.py b/unit/models/returnAch.py index 7f22934d..6c972246 100644 --- a/unit/models/returnAch.py +++ b/unit/models/returnAch.py @@ -2,7 +2,7 @@ from typing import Literal from unit.models import * -AchReturnReason = Literal["InsufficientFunds", "Unauthorized"] +AchReturnReason = Literal["InsufficientFunds", "Unauthorized", "UncollectedFunds"] class ReturnReceivedAchTransactionRequest(UnitRequest): From 46f86058b939302d97f94d4b5d9d50cd8d9c19e2 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Mar 2022 12:27:20 -0800 Subject: [PATCH 043/181] added coded --- unit/models/codecs.py | 3 +++ unit/models/event.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 12cdce47..7b84e713 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -206,6 +206,9 @@ "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), diff --git a/unit/models/event.py b/unit/models/event.py index 568e664f..51a9239a 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -267,6 +267,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 PaymentClearingEvent(_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.created' + 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]], From 4c1b76958fec7c0d72e969e53372ce666fd94a2f Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Mar 2022 14:19:59 -0800 Subject: [PATCH 044/181] added codecs --- unit/models/codecs.py | 6 ++++++ unit/models/event.py | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 7b84e713..30462550 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -215,6 +215,9 @@ "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), @@ -224,6 +227,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), diff --git a/unit/models/event.py b/unit/models/event.py index 51a9239a..c9757553 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -242,6 +242,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 CustomerCreatedEvent(_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]]): @@ -328,15 +341,15 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("tags"), relationships) class PaymentRejectedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], + 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["previousStatus"] = previous_status + self.attributes["reason"] = previous_status @staticmethod def from_json_api(_id, _type, attributes, relationships): - return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["reason"], attributes.get("tags"), relationships) class StatementsCreatedEvent(BaseEvent): From 1a5fdbc81f35fd4d3513de35c9d291f92b4901ec Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 10 Mar 2022 14:41:30 -0800 Subject: [PATCH 045/181] coded update --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index c9757553..a560db3c 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -345,7 +345,7 @@ def __init__(self, id: str, created_at: datetime, reason: str, tags: Optional[Di relationships: Optional[Dict[str, Relationship]]): BaseEvent.__init__(self, id, created_at, tags, relationships) self.type = 'payment.rejected' - self.attributes["reason"] = previous_status + self.attributes["reason"] = reason @staticmethod def from_json_api(_id, _type, attributes, relationships): From 723e89afe325a34e0c066561a869503e45329e77 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 18 Mar 2022 14:02:38 -0700 Subject: [PATCH 046/181] adds support for /sandbox/applications/application_id/approve --- unit/api/application_resource.py | 17 +++++++++++++++++ unit/models/application.py | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 7156c7d3..7c0a6f22 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: ApproveApplicationSB): + 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/models/application.py b/unit/models/application.py index cca5a2d1..458c7d84 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -262,6 +262,7 @@ 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): @@ -285,3 +286,21 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) + +class ApproveApplicationSB(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()) From 93768ff2eb0d6753b71e77a93da6da7925b321d1 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 18 Mar 2022 14:16:45 -0700 Subject: [PATCH 047/181] ApproveApplicationSBRequest --- unit/api/application_resource.py | 2 +- unit/models/application.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 7c0a6f22..82d041e7 100644 --- a/unit/api/application_resource.py +++ b/unit/api/application_resource.py @@ -71,7 +71,7 @@ def update(self, request: PatchApplicationRequest) -> Union[UnitResponse[Applica else: return UnitError.from_json_api(response.json()) - def approve_sb(self, request: ApproveApplicationSB): + def approve_sb(self, request: ApproveApplicationSBRequest): url = f"sandbox/{self.resource}/{request.application_id}/approve" payload = request.to_json_api() diff --git a/unit/models/application.py b/unit/models/application.py index 458c7d84..db22406a 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -287,7 +287,7 @@ def __repr__(self): json.dumps(self.to_json_api()) -class ApproveApplicationSB(UnitRequest): +class ApproveApplicationSBRequest(UnitRequest): def __init__(self, application_id: str): self.application_id = application_id From 0b6783168283d94141775fc7c9423f8a23d342d1 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 22 Mar 2022 18:22:42 -0700 Subject: [PATCH 048/181] update to authorization requests --- unit/models/authorization_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unit/models/authorization_request.py b/unit/models/authorization_request.py index 6eed39a4..f654701d 100644 --- a/unit/models/authorization_request.py +++ b/unit/models/authorization_request.py @@ -34,7 +34,7 @@ def from_json_api(_id, _type, attributes, relationships): 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 From 5f115cd7c652a5af406ce33813c4506953ad1d55 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 24 Mar 2022 15:20:36 -0700 Subject: [PATCH 049/181] card dto update --- unit/models/card.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index 5e7b7699..1919acca 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -70,15 +70,15 @@ 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, 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, "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( @@ -93,7 +93,7 @@ def from_json_api(_id, _type, attributes, relationships): Card = Union[IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO, BusinessVirtualDebitCardDTO] -class CreateIndividualDebitCard(object): +class CreateIndividualDebitCard(UnitRequest): 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): @@ -129,7 +129,7 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessDebitCard(object): +class CreateBusinessDebitCard(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], @@ -190,7 +190,7 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateIndividualVirtualDebitCard(object): +class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None): self.idempotency_key = idempotency_key @@ -218,11 +218,11 @@ def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessVirtualDebitCard(object): +class CreateBusinessVirtualDebitCard(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]]): + status: CardStatus, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, + idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, + relationships: Optional[Dict[str, Relationship]] = None): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -233,8 +233,8 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.passport = passport self.nationality = nationality self.idempotency_key = idempotency_key - self.tags = tags - self.relationships = relationships + self.tags = tags or {} + self.relationships = relationships or {} def to_json_api(self) -> Dict: payload = { From 5c77ebb643be1b2c8bc884b1c54b4283da4321eb Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 1 Apr 2022 09:14:57 -0700 Subject: [PATCH 050/181] fix bug --- unit/models/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/card.py b/unit/models/card.py index 1919acca..baa74b69 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -83,7 +83,7 @@ def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration 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"], 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 From 1c78ee17319257a285cb111d0713546456dd3650 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 6 Apr 2022 15:52:41 +0100 Subject: [PATCH 051/181] added authorization canceled event --- unit/models/event.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index a560db3c..6326c055 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -73,20 +73,38 @@ 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 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: Dict[str, str], + 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"], 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, @@ -392,7 +410,7 @@ def from_json_api(_id, _type, attributes, relationships): EventDTO = Union[AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, - AuthorizationCreatedEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, + AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, From fd64f5d7fe57db5bb3b404b06cac6e4d477bcf00 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 7 Apr 2022 13:30:44 +0100 Subject: [PATCH 052/181] added limits to card create card request --- unit/models/card.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index baa74b69..d647967f 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -94,11 +94,12 @@ def from_json_api(_id, _type, attributes, relationships): class CreateIndividualDebitCard(UnitRequest): - 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): + def __init__(self, relationships: Dict[str, Relationship], limits: Optional[Cardlimits] = 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 +116,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 @@ -131,23 +135,25 @@ def __repr__(self): class CreateBusinessDebitCard(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]]): + status: CardStatus, limits: Optional[CardLimits] = None, 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, relationships: Optional[Dict[str, Relationship]] = 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.limits = limits self.shipping_address = shipping_address self.ssn = ssn self.passport = passport self.nationality = nationality self.design = design self.idempotency_key = idempotency_key - self.tags = tags - self.relationships = relationships + self.tags = tags or {} + self.relationships = relationships or {} def to_json_api(self) -> Dict: payload = { @@ -167,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.ssn: payload["data"]["attributes"]["ssn"] = self.ssn @@ -192,8 +201,9 @@ def __repr__(self): class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, - tags: Optional[Dict[str, str]] = None): + limits: Optional[CardLimits] = None, tags: Optional[Dict[str, str]] = None): self.idempotency_key = idempotency_key + self.limits = limits self.tags = tags self.relationships = relationships @@ -209,6 +219,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 @@ -220,7 +233,8 @@ def __repr__(self): class CreateBusinessVirtualDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, + status: CardStatus, limits: Optional[CardLimits] = None, ssn: Optional[str] = None, + passport: Optional[str] = None, nationality: Optional[str] = None, idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, relationships: Optional[Dict[str, Relationship]] = None): self.full_name = full_name @@ -231,6 +245,7 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p self.status = status self.ssn = ssn self.passport = passport + self.limits = limits self.nationality = nationality self.idempotency_key = idempotency_key self.tags = tags or {} @@ -254,6 +269,9 @@ def to_json_api(self) -> Dict: if self.ssn: payload["data"]["attributes"]["ssn"] = self.ssn + if self.limits: + limits["data"]["attributes"]["limits"] = self.limits + if self.passport: payload["data"]["attributes"]["passport"] = self.passport From b394e8c5dc0983fea4e69347c6a8177e76530eb5 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 6 Apr 2022 14:24:53 -0700 Subject: [PATCH 053/181] Adds support for simulating incoming ACH --- unit/api/payment_resource.py | 12 +++++++++++- unit/models/codecs.py | 4 ++-- unit/models/payment.py | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/unit/api/payment_resource.py b/unit/api/payment_resource.py index 6ef30a43..33c13fda 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): @@ -45,3 +45,13 @@ 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()) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 30462550..6c5eb98e 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -9,8 +9,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 from unit.models.customerToken import CustomerTokenDTO, CustomerVerificationTokenDTO from unit.models.fee import FeeDTO from unit.models.event import * diff --git a/unit/models/payment.py b/unit/models/payment.py index 2548d980..694f7388 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -33,6 +33,29 @@ 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: AchStatus, 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 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]], @@ -172,6 +195,20 @@ 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 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], From 6d87d0c114c9ff3ec853ae2200440dc3a0667b28 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 8 Apr 2022 12:36:14 +0100 Subject: [PATCH 054/181] card limits --- unit/models/card.py | 40 ++++++++++++++++++++++++++++------------ unit/models/codecs.py | 7 +++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index d647967f..fca40cd4 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -94,7 +94,7 @@ def from_json_api(_id, _type, attributes, relationships): class CreateIndividualDebitCard(UnitRequest): - def __init__(self, relationships: Dict[str, Relationship], limits: Optional[Cardlimits] = None, + 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 @@ -135,7 +135,7 @@ def __repr__(self): class CreateBusinessDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLimits] = None, shipping_address: Optional[Address] = None, + status: CardStatus, limits: Optional[CardLevelLimits] = None, 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, relationships: Optional[Dict[str, Relationship]] = None): @@ -201,7 +201,7 @@ def __repr__(self): class CreateIndividualVirtualDebitCard(UnitRequest): def __init__(self, relationships: Dict[str, Relationship], idempotency_key: Optional[str] = None, - limits: Optional[CardLimits] = 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 @@ -233,7 +233,7 @@ def __repr__(self): class CreateBusinessVirtualDebitCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLimits] = None, ssn: Optional[str] = None, + status: CardStatus, limits: Optional[CardLevelLimits] = None, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, relationships: Optional[Dict[str, Relationship]] = None): @@ -293,12 +293,13 @@ def __repr__(self): CreateCardRequest = Union[CreateIndividualDebitCard, CreateBusinessDebitCard, CreateIndividualVirtualDebitCard, CreateBusinessVirtualDebitCard] -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: @@ -312,6 +313,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 @@ -324,12 +328,13 @@ def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessDebitCard(object): +class PatchBusinessDebitCard(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): + limits: CardLevelLimits = None, tags: Optional[Dict[str, str]] = None): self.card_id = card_id self.shipping_address = shipping_address + self.limits = limits self.design = design self.tags = tags @@ -344,6 +349,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.address: payload["data"]["attributes"]["address"] = self.address @@ -364,9 +372,10 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class PatchIndividualVirtualDebitCard(object): - def __init__(self, card_id: str, tags: Optional[Dict[str, str]] = None): +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: @@ -377,6 +386,9 @@ def to_json_api(self) -> Dict: } } + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.tags: payload["data"]["attributes"]["tags"] = self.tags @@ -385,11 +397,12 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessVirtualDebitCard(object): +class PatchBusinessVirtualDebitCard(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, limits: CardLevelLimits = None, tags: Optional[Dict[str, str]] = None): self.card_id = card_id self.address = address + self.limits = limits self.phone = phone self.email = email self.tags = tags @@ -402,6 +415,9 @@ def to_json_api(self) -> Dict: } } + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + if self.address: payload["data"]["attributes"]["address"] = self.address diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 6c5eb98e..cbcfadd9 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -323,6 +323,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): From b160a0e9f36741a5cd5b1f568465bca329ba7701 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 11 Apr 2022 16:47:20 +0100 Subject: [PATCH 055/181] patch card payload --- unit/models/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/card.py b/unit/models/card.py index fca40cd4..993eb6e0 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -270,7 +270,7 @@ def to_json_api(self) -> Dict: payload["data"]["attributes"]["ssn"] = self.ssn if self.limits: - limits["data"]["attributes"]["limits"] = self.limits + payload["data"]["attributes"]["limits"] = self.limits if self.passport: payload["data"]["attributes"]["passport"] = self.passport From 82f41ccc46e303c2cf91e29e7073f7f7dd5205b2 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 12 Apr 2022 12:18:45 +0100 Subject: [PATCH 056/181] fix unit bug --- unit/models/card.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/unit/models/card.py b/unit/models/card.py index 993eb6e0..09341f5c 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -389,8 +389,7 @@ def to_json_api(self) -> Dict: if self.limits: payload["data"]["attributes"]["limits"] = self.limits - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + payload["data"]["attributes"]["tags"] = self.tags or {} return payload @@ -427,8 +426,7 @@ def to_json_api(self) -> Dict: if self.email: payload["data"]["attributes"]["email"] = self.email - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + payload["data"]["attributes"]["tags"] = self.tags or {} return payload From 129a913acdca6eb97dee5425d52a6f943d1fdb29 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 22 Apr 2022 10:11:34 -0700 Subject: [PATCH 057/181] fix payment rejected type --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 6326c055..c690e22c 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -314,7 +314,7 @@ 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.created' + self.type = 'payment.rejected' self.attributes["status"] = status @staticmethod From 3d562a718164779f1e241c3881e7b085ffbfb980 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 22 Apr 2022 10:26:24 -0700 Subject: [PATCH 058/181] PaymentCreated event returning Payment clearing event --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index c690e22c..84b90b93 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -307,7 +307,7 @@ def __init__(self, id: str, created_at: datetime, status: str, tags: Optional[Di @staticmethod def from_json_api(_id, _type, attributes, relationships): - return PaymentClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + return PaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], attributes.get("tags"), relationships) class PaymentRejectedEvent(BaseEvent): From c91391223100e4a2902083a326c8f0c363e1138f Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 2 May 2022 17:39:22 -0700 Subject: [PATCH 059/181] fix benificial owner bug --- unit/models/benificial_owner.py | 26 ++++++++++++++++++++++++++ unit/models/codecs.py | 6 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 unit/models/benificial_owner.py 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/codecs.py b/unit/models/codecs.py index cbcfadd9..63b52fcb 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -24,6 +24,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: @@ -262,7 +263,10 @@ "pinStatus": lambda _id, _type, attributes, relationships: PinStatusDTO.from_json_api(attributes), - } + + "beneficialOwner": lambda _id, _type, attributes, relationships: + BenificialOwnerDTO.from_json_api(attributes), +} def split_json_api_single_response(payload: Dict): From dfddb0f449a31b9f47949422cc0ba65f2a532006 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 10 May 2022 10:34:31 -0700 Subject: [PATCH 060/181] remove duplicate decoder --- unit/models/codecs.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 558883c7..4e91c277 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -189,9 +189,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), From b2a008d916127151efc94093c60adb9474ff1e3f Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 10 May 2022 11:35:46 -0700 Subject: [PATCH 061/181] fix package --- unit/models/payment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 694f7388..4c05d6a1 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -34,7 +34,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("tags"), relationships) class SimulateIncomingAchPaymentDTO(BasePayment): - def __init__(self, id: str, created_at: datetime, status: AchStatus, direction: str, + 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]]): @@ -87,7 +87,7 @@ def from_json_api(_id, _type, attributes, 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 From 2e4a95db3a20d48021179175ad40f9d0229fd900 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 10 May 2022 16:37:12 -0700 Subject: [PATCH 062/181] fix func signature --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 5acbcb40..11e33c03 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -402,7 +402,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' From 62f6318a4cf2585b062e32750bf797db3cc6a76a Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 30 May 2022 12:51:03 -0700 Subject: [PATCH 063/181] card authorization patches --- unit/models/__init__.py | 5 ++++- unit/models/authorization.py | 13 +++++------- unit/models/event.py | 38 ++++++++++++++++++++++++------------ 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index f5589981..ecef06b8 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -222,7 +222,10 @@ def __init__(self, longitude: int, latitude: int): @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): 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/event.py b/unit/models/event.py index 11e33c03..e7399f10 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -90,7 +90,7 @@ def from_json_api(_id, _type, attributes, relationships): relationships) class AuthorizationDeclinedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, merchant: Dict[str, str], + 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) @@ -106,11 +106,11 @@ 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=attributes["merchant"], reason=attributes.get("reason"), recurring=attributes["recurring"], + 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, amount: int, card_last_4_digits: str, merchant: Dict[str, str], + 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' @@ -122,7 +122,7 @@ def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digit @staticmethod def from_json_api(_id, _type, attributes, relationships): return AuthorizationCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["amount"], attributes["cardLast4Digits"], attributes["merchant"], + attributes["amount"], attributes["cardLast4Digits"], Merchant.from_json_api(attributes["merchant"]), attributes["recurring"], attributes.get("tags"), relationships) class AuthorizationRequestApprovedEvent(BaseEvent): @@ -172,13 +172,16 @@ def from_json_api(_id, _type, attributes, 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 @@ -186,10 +189,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]], @@ -429,7 +442,8 @@ def from_json_api(_id, _type, attributes, relationships): EventDTO = Union[AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, - AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, + AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, + AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, From cd2378fd9038bbb1c93d402862b789bd497220ad Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 6 Jun 2022 17:27:55 -0700 Subject: [PATCH 064/181] fix merchant type bug --- unit/models/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index ecef06b8..fb288cbe 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -229,7 +229,7 @@ def from_json_api(data: Dict): class Merchant(object): - def __init__(self, name: str, type: int, category: str, location: Optional[str]): + def __init__(self, name: str, type: int, category: Optional[str], location: Optional[str]): self.name = name self.type = type self.category = category @@ -237,7 +237,7 @@ def __init__(self, name: str, type: int, category: str, location: Optional[str]) @staticmethod def from_json_api(data: Dict): - return Merchant(data["name"], data["type"], data["category"], data.get("location")) + return Merchant(data["name"], data["type"], data.get("category"), data.get("location")) class CardLevelLimits(object): def __init__(self, daily_withdrawal: int, daily_purchase: int, monthly_withdrawal: int, monthly_purchase: int): From d363d96f1113c791cfc07d9714c92cb9ff80f6a8 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 7 Jun 2022 11:19:58 -0700 Subject: [PATCH 065/181] added authorization canceled to coded --- unit/models/codecs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 4e91c277..2fa48f45 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -177,6 +177,9 @@ "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), From 38c8208e03e44b02cbfdb3f8c3d24651eac66623 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Thu, 9 Jun 2022 12:01:00 -0700 Subject: [PATCH 066/181] fix coordinate bug --- unit/models/transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index b0d7efe6..58aec0fa 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -154,7 +154,7 @@ def from_json_api(_id, _type, attributes, relationships): 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"]), + Merchant.from_json_api(attributes["merchant"]), Coordinates.from_json_api(attributes.get("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"), From 37da55f778f5d537f72cd0e412df758e1521c0df Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 14 Jun 2022 12:31:19 -0700 Subject: [PATCH 067/181] added rewards to sdk --- unit/api/reward_resource.py | 39 ++++++++++ unit/models/codecs.py | 6 ++ unit/models/reward.py | 138 ++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 unit/api/reward_resource.py create mode 100644 unit/models/reward.py 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/models/codecs.py b/unit/models/codecs.py index 2fa48f45..ed7d296e 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -1,6 +1,8 @@ import json from unit.models import * from datetime import datetime, date + +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 @@ -269,6 +271,10 @@ "beneficialOwner": lambda _id, _type, attributes, relationships: BenificialOwnerDTO.from_json_api(attributes), + + "reward": lambda _id, _type, attributes, relationships: + RewardDTO.from_json_api(_id, attributes, relationships), + } diff --git a/unit/models/reward.py b/unit/models/reward.py new file mode 100644 index 00000000..995fa25f --- /dev/null +++ b/unit/models/reward.py @@ -0,0 +1,138 @@ +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, tags: Optional[Dict[str, str]] = None, + relationships: Optional[Dict[str, Relationship]] = None): + self.id = id + self.type = "reward" + self.attributes = {"amount": amount, "description": description, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, attributes, relationships): + return RewardDTO(_id, attributes["amount"], attributes["description"], attributes.get("tags"), relationships) + + +class CreateRewardRequest(UnitRequest): + def __init__( + self, + amount: int, + description: str, + receiving_account_id: str, + rewarded_transaction_id: Optional[str], + 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 + + def to_json_api(self) -> Dict: + relationships = { + "receivingAccount": Relationship(_type="depositAccount", _id=self.receiving_account_id) + } + + if self.rewarded_transaction_id: + relationships["rewardedTransaction"] = Relationship(_type="transaction", _id=self.rewarded_transaction_id) + + if self.funding_account_id: + relationships["fundingAccount"] = Relationship(_type="depositAccount", _id=self.funding_account_id) + + payload = { + "data": { + "type": self.type, + "attributes": { + "amount": self.amount, + "description": self.description + }, + "relationships": 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 From 7246c06fadc44bec79c1869b3cba1948d2498256 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Wed, 15 Jun 2022 15:19:27 -0700 Subject: [PATCH 068/181] reward patches --- unit/__init__.py | 2 ++ unit/models/reward.py | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/unit/__init__.py b/unit/__init__.py index 8076f8c6..968dc249 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -19,6 +19,7 @@ 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 __all__ = ["api", "models", "utils"] @@ -46,3 +47,4 @@ 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) diff --git a/unit/models/reward.py b/unit/models/reward.py index 995fa25f..3e3a4246 100644 --- a/unit/models/reward.py +++ b/unit/models/reward.py @@ -10,16 +10,16 @@ class RewardDTO(object): - def __init__(self, id: str, amount: int, description: str, tags: Optional[Dict[str, str]] = None, + 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, "tags": tags} + 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.get("tags"), relationships) + return RewardDTO(_id, attributes["amount"], attributes["description"], attributes["status"], attributes.get("tags"), relationships) class CreateRewardRequest(UnitRequest): @@ -28,7 +28,7 @@ def __init__( amount: int, description: str, receiving_account_id: str, - rewarded_transaction_id: Optional[str], + rewarded_transaction_id: Optional[str] = None, funding_account_id: Optional[str] = None, idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None @@ -42,17 +42,16 @@ def __init__( self.idempotency_key = idempotency_key self.tags = tags - def to_json_api(self) -> Dict: - relationships = { - "receivingAccount": Relationship(_type="depositAccount", _id=self.receiving_account_id) + self.relationships = { + "receivingAccount": Relationship(_type="depositaccount", _id=self.receiving_account_id) } - if self.rewarded_transaction_id: - relationships["rewardedTransaction"] = Relationship(_type="transaction", _id=self.rewarded_transaction_id) + self.relationships["rewardedTransaction"] = Relationship(_type="transaction", _id=self.rewarded_transaction_id) if self.funding_account_id: - relationships["fundingAccount"] = Relationship(_type="depositAccount", _id=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, @@ -60,7 +59,7 @@ def to_json_api(self) -> Dict: "amount": self.amount, "description": self.description }, - "relationships": relationships + "relationships": self.relationships } } From 85af67ba1591eb2e190f7821f28ab2c6a298984f Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Mon, 27 Jun 2022 17:17:34 -0700 Subject: [PATCH 069/181] reward bug fix --- unit/models/reward.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/reward.py b/unit/models/reward.py index 3e3a4246..07f1535b 100644 --- a/unit/models/reward.py +++ b/unit/models/reward.py @@ -43,7 +43,7 @@ def __init__( self.tags = tags self.relationships = { - "receivingAccount": Relationship(_type="depositaccount", _id=self.receiving_account_id) + "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) From 29feabecdb11e42e8c811bcb010b4d2d7196f200 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Tue, 26 Jul 2022 18:04:31 -0700 Subject: [PATCH 070/181] fixed payment rejected bug --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index e7399f10..5fabc739 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -399,7 +399,7 @@ def __init__(self, id: str, created_at: datetime, reason: str, tags: Optional[Di @staticmethod def from_json_api(_id, _type, attributes, relationships): - return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["reason"], + return PaymentRejectedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["reason"], attributes.get("tags"), relationships) class StatementsCreatedEvent(BaseEvent): From ce9b05aa87f664636ac69166cab88bd284e2e484 Mon Sep 17 00:00:00 2001 From: Dragon Prevost Date: Fri, 29 Jul 2022 10:09:19 -0700 Subject: [PATCH 071/181] fix customer updated codec bug --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 5fabc739..e6fcfa72 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -301,7 +301,7 @@ def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]] @staticmethod def from_json_api(_id, _type, attributes, relationships): - return CustomerCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + return CustomerUpdatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), relationships) From 915e4d0d118358cd50d6d9f735aebfe41e345536 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 16 Sep 2022 17:46:18 -0700 Subject: [PATCH 072/181] Patch CreateCounterpartyRequest to inherit UnitRequest --- unit/models/counterparty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/counterparty.py b/unit/models/counterparty.py index c91ecb8d..e043b414 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): From cc30baadeea24605856b4f77a32df068e969841d Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 11 Jan 2023 17:31:52 -0800 Subject: [PATCH 073/181] Simulate transmitting and clearing ach payments --- unit/__init__.py | 2 ++ unit/api/ach_resource.py | 34 +++++++++++++++++++ unit/models/payment.py | 72 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 unit/api/ach_resource.py diff --git a/unit/__init__.py b/unit/__init__.py index 968dc249..99bda338 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -4,6 +4,7 @@ from unit.api.card_resource import CardResource from unit.api.transaction_resource import TransactionResource from unit.api.payment_resource import PaymentResource +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 @@ -32,6 +33,7 @@ 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.ach = AchResource(api_url, token) self.statements = StatementResource(api_url, token) self.customerTokens = CustomerTokenResource(api_url, token) self.counterparty = CounterpartyResource(api_url, token) 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/models/payment.py b/unit/models/payment.py index 4c05d6a1..502fb3f0 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -56,6 +56,30 @@ def from_json_api(_id, _type, attributes, relationships): 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]], @@ -209,6 +233,54 @@ def to_json_api(self) -> Dict: 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], From a3c64d0d538cb9dfaf19807149bf8e958e1b2a5a Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Tue, 13 Jun 2023 17:02:07 -0400 Subject: [PATCH 074/181] Update CreateBusinessApplicationRequest with new required compliance fields --- unit/models/application.py | 436 +++++++++++++++++++++++++++++-------- 1 file changed, 347 insertions(+), 89 deletions(-) diff --git a/unit/models/application.py b/unit/models/application.py index db22406a..bfb450b7 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -4,76 +4,265 @@ ApplicationStatus = Literal["Approved", "Denied", "Pending", "PendingReview"] -DocumentType = Literal["IdDocument", "Passport", "AddressVerification", "CertificateOfIncorporation", - "EmployerIdentificationNumberConfirmation"] +DocumentType = Literal[ + "IdDocument", + "Passport", + "AddressVerification", + "CertificateOfIncorporation", + "EmployerIdentificationNumberConfirmation", +] + +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], + 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, + "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("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], + 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, + "ssn": ssn, + "message": message, + "ip": ip, + "ein": ein, + "entityType": entity_type, + "dba": dba, + "contact": contact, + "officer": officer, + "beneficialOwners": beneficial_owners, + "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("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, + ): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -100,7 +289,7 @@ def to_json_api(self) -> Dict: "address": self.address, "email": self.email, "phone": self.phone, - } + }, } } @@ -114,7 +303,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,7 +320,9 @@ 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 @@ -142,20 +335,29 @@ 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, - tags: Optional[Dict[str, str]] = None, - dba: str = None, - ip: str = None, - website: str = None, + 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 ): self.name = name self.address = address @@ -170,6 +372,15 @@ def __init__( 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 def to_json_api(self) -> dict: payload = { @@ -185,6 +396,14 @@ def to_json_api(self) -> dict: "officer": self.officer, "beneficialOwners": self.beneficial_owners, "entityType": self.entity_type, + "industry": self.industry, + "annualRevenue": self.annual_revenue, + "numberOfEmployees": self.number_of_employees, + "cashFlow": self.cash_flow, + "yearOfIncorporation": self.year_of_incorporation, + "countriesOfOperation": self.countries_of_operation, + "stockSymbol": self.stock_symbol, + "businessVertical": self.business_vertical, }, } } @@ -208,30 +427,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: 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], + ): 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 @@ -240,9 +498,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 @@ -264,19 +528,18 @@ def to_dict(self) -> Dict: 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 @@ -293,12 +556,7 @@ def __init__(self, application_id: str): def to_json_api(self) -> Dict: payload = { - "data": { - "type": "applicationApprove", - "attributes": { - "reason": "sandbox" - } - } + "data": {"type": "applicationApprove", "attributes": {"reason": "sandbox"}} } return payload From 064660f1a2f5cedbca4c7baac3b545381b94ab52 Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Fri, 7 Jul 2023 15:02:29 -0400 Subject: [PATCH 075/181] Add relevant models and operations for charge cards and repayments --- build/lib/unit/__init__.py | 52 ++ build/lib/unit/api/__init__.py | 0 .../unit/api/account_end_of_day_resource.py | 19 + build/lib/unit/api/account_resource.py | 71 ++ build/lib/unit/api/ach_resource.py | 34 + build/lib/unit/api/api_token_resource.py | 34 + .../lib/unit/api/applicationForm_resource.py | 37 ++ build/lib/unit/api/application_resource.py | 90 +++ build/lib/unit/api/atmLocation_resource.py | 34 + .../api/authorization_request_resource.py | 46 ++ build/lib/unit/api/authorization_resource.py | 28 + build/lib/unit/api/base_resource.py | 44 ++ build/lib/unit/api/bill_pay_resource.py | 22 + build/lib/unit/api/card_resource.py | 113 ++++ build/lib/unit/api/counterparty_resource.py | 59 ++ build/lib/unit/api/customerToken_resource.py | 29 + build/lib/unit/api/customer_resource.py | 40 ++ build/lib/unit/api/event_resource.py | 33 + build/lib/unit/api/fee_resource.py | 19 + build/lib/unit/api/institution_resource.py | 17 + build/lib/unit/api/payment_resource.py | 57 ++ .../lib/unit/api/received_payment_resource.py | 45 ++ build/lib/unit/api/repayment_resource.py | 38 ++ build/lib/unit/api/returnAch_resource.py | 20 + build/lib/unit/api/reward_resource.py | 39 ++ build/lib/unit/api/statement_resource.py | 38 ++ build/lib/unit/api/transaction_resource.py | 41 ++ build/lib/unit/api/webhook_resource.py | 72 ++ build/lib/unit/models/__init__.py | 301 +++++++++ build/lib/unit/models/account.py | 344 ++++++++++ build/lib/unit/models/account_end_of_day.py | 41 ++ build/lib/unit/models/api_token.py | 48 ++ build/lib/unit/models/application.py | 564 ++++++++++++++++ build/lib/unit/models/applicationForm.py | 103 +++ build/lib/unit/models/atm_location.py | 28 + build/lib/unit/models/authorization.py | 61 ++ .../lib/unit/models/authorization_request.py | 98 +++ build/lib/unit/models/benificial_owner.py | 26 + build/lib/unit/models/bill_pay.py | 21 + build/lib/unit/models/card.py | 617 ++++++++++++++++++ build/lib/unit/models/codecs.py | 403 ++++++++++++ build/lib/unit/models/counterparty.py | 172 +++++ build/lib/unit/models/customer.py | 158 +++++ build/lib/unit/models/customerToken.py | 89 +++ build/lib/unit/models/event.py | 463 +++++++++++++ build/lib/unit/models/fee.py | 50 ++ build/lib/unit/models/institution.py | 17 + build/lib/unit/models/payment.py | 450 +++++++++++++ build/lib/unit/models/repayment.py | 114 ++++ build/lib/unit/models/returnAch.py | 29 + build/lib/unit/models/reward.py | 137 ++++ build/lib/unit/models/statement.py | 46 ++ build/lib/unit/models/transaction.py | 466 +++++++++++++ build/lib/unit/models/webhook.py | 92 +++ build/lib/unit/utils/__init__.py | 0 build/lib/unit/utils/date_utils.py | 13 + unit/api/account_resource.py | 4 +- unit/api/repayment_resource.py | 38 ++ unit/models/__init__.py | 23 + unit/models/account.py | 114 +++- unit/models/card.py | 256 ++++++-- unit/models/repayment.py | 114 ++++ unit_python_sdk.egg-info/PKG-INFO | 18 + unit_python_sdk.egg-info/SOURCES.txt | 65 ++ unit_python_sdk.egg-info/dependency_links.txt | 1 + unit_python_sdk.egg-info/requires.txt | 1 + unit_python_sdk.egg-info/top_level.txt | 1 + 67 files changed, 6680 insertions(+), 77 deletions(-) create mode 100644 build/lib/unit/__init__.py create mode 100644 build/lib/unit/api/__init__.py create mode 100644 build/lib/unit/api/account_end_of_day_resource.py create mode 100644 build/lib/unit/api/account_resource.py create mode 100644 build/lib/unit/api/ach_resource.py create mode 100644 build/lib/unit/api/api_token_resource.py create mode 100644 build/lib/unit/api/applicationForm_resource.py create mode 100644 build/lib/unit/api/application_resource.py create mode 100644 build/lib/unit/api/atmLocation_resource.py create mode 100644 build/lib/unit/api/authorization_request_resource.py create mode 100644 build/lib/unit/api/authorization_resource.py create mode 100644 build/lib/unit/api/base_resource.py create mode 100644 build/lib/unit/api/bill_pay_resource.py create mode 100644 build/lib/unit/api/card_resource.py create mode 100644 build/lib/unit/api/counterparty_resource.py create mode 100644 build/lib/unit/api/customerToken_resource.py create mode 100644 build/lib/unit/api/customer_resource.py create mode 100644 build/lib/unit/api/event_resource.py create mode 100644 build/lib/unit/api/fee_resource.py create mode 100644 build/lib/unit/api/institution_resource.py create mode 100644 build/lib/unit/api/payment_resource.py create mode 100644 build/lib/unit/api/received_payment_resource.py create mode 100644 build/lib/unit/api/repayment_resource.py create mode 100644 build/lib/unit/api/returnAch_resource.py create mode 100644 build/lib/unit/api/reward_resource.py create mode 100644 build/lib/unit/api/statement_resource.py create mode 100644 build/lib/unit/api/transaction_resource.py create mode 100644 build/lib/unit/api/webhook_resource.py create mode 100644 build/lib/unit/models/__init__.py create mode 100644 build/lib/unit/models/account.py create mode 100644 build/lib/unit/models/account_end_of_day.py create mode 100644 build/lib/unit/models/api_token.py create mode 100644 build/lib/unit/models/application.py create mode 100644 build/lib/unit/models/applicationForm.py create mode 100644 build/lib/unit/models/atm_location.py create mode 100644 build/lib/unit/models/authorization.py create mode 100644 build/lib/unit/models/authorization_request.py create mode 100644 build/lib/unit/models/benificial_owner.py create mode 100644 build/lib/unit/models/bill_pay.py create mode 100644 build/lib/unit/models/card.py create mode 100644 build/lib/unit/models/codecs.py create mode 100644 build/lib/unit/models/counterparty.py create mode 100644 build/lib/unit/models/customer.py create mode 100644 build/lib/unit/models/customerToken.py create mode 100644 build/lib/unit/models/event.py create mode 100644 build/lib/unit/models/fee.py create mode 100644 build/lib/unit/models/institution.py create mode 100644 build/lib/unit/models/payment.py create mode 100644 build/lib/unit/models/repayment.py create mode 100644 build/lib/unit/models/returnAch.py create mode 100644 build/lib/unit/models/reward.py create mode 100644 build/lib/unit/models/statement.py create mode 100644 build/lib/unit/models/transaction.py create mode 100644 build/lib/unit/models/webhook.py create mode 100644 build/lib/unit/utils/__init__.py create mode 100644 build/lib/unit/utils/date_utils.py create mode 100644 unit/api/repayment_resource.py create mode 100644 unit/models/repayment.py create mode 100644 unit_python_sdk.egg-info/PKG-INFO create mode 100644 unit_python_sdk.egg-info/SOURCES.txt create mode 100644 unit_python_sdk.egg-info/dependency_links.txt create mode 100644 unit_python_sdk.egg-info/requires.txt create mode 100644 unit_python_sdk.egg-info/top_level.txt diff --git a/build/lib/unit/__init__.py b/build/lib/unit/__init__.py new file mode 100644 index 00000000..99bda338 --- /dev/null +++ b/build/lib/unit/__init__.py @@ -0,0 +1,52 @@ +from unit.api.application_resource import ApplicationResource +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.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 +from unit.api.returnAch_resource import ReturnAchResource +from unit.api.applicationForm_resource import ApplicationFormResource +from unit.api.fee_resource import FeeResource +from unit.api.event_resource import EventResource +from unit.api.webhook_resource import WebhookResource +from unit.api.institution_resource import InstitutionResource +from unit.api.atmLocation_resource import AtmLocationResource +from unit.api.bill_pay_resource import BillPayResource +from unit.api.api_token_resource import APITokenResource +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 + +__all__ = ["api", "models", "utils"] + + +class Unit(object): + def __init__(self, api_url, token): + self.applications = ApplicationResource(api_url, token) + self.customers = CustomerResource(api_url, token) + self.accounts = AccountResource(api_url, token) + self.cards = CardResource(api_url, token) + self.transactions = TransactionResource(api_url, token) + self.payments = PaymentResource(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) + self.returnAch = ReturnAchResource(api_url, token) + self.applicationForms = ApplicationFormResource(api_url, token) + self.fees = FeeResource(api_url, token) + self.events = EventResource(api_url, token) + self.webhooks = WebhookResource(api_url, token) + self.institutions = InstitutionResource(api_url, token) + self.atmLocations = AtmLocationResource(api_url, token) + self.billPays = BillPayResource(api_url, token) + self.api_tokens = APITokenResource(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) diff --git a/build/lib/unit/api/__init__.py b/build/lib/unit/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build/lib/unit/api/account_end_of_day_resource.py b/build/lib/unit/api/account_end_of_day_resource.py new file mode 100644 index 00000000..71490714 --- /dev/null +++ b/build/lib/unit/api/account_end_of_day_resource.py @@ -0,0 +1,19 @@ +from unit.api.base_resource import BaseResource +from unit.models.account_end_of_day import * +from unit.models.codecs import DtoDecoder + + +class AccountEndOfDayResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "account-end-of-day" + + def list(self, params: ListAccountEndOfDayParams = None) -> Union[UnitResponse[List[AccountEndOfDayDTO]], UnitError]: + params = params or ListAccountEndOfDayParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AccountEndOfDayDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/account_resource.py b/build/lib/unit/api/account_resource.py new file mode 100644 index 00000000..a034469d --- /dev/null +++ b/build/lib/unit/api/account_resource.py @@ -0,0 +1,71 @@ +from unit.api.base_resource import BaseResource +from unit.models.account import * +from unit.models.codecs import DtoDecoder + +class AccountResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "accounts" + + 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): + data = response.json().get("data") + return UnitResponse[AccountDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def close_account(self, request: CloseAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.account_id}/close", 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 reopen_account(self, account_id: str, reason: str = "ByCustomer") -> Union[UnitResponse[AccountDTO], UnitError]: + response = super().post(f"{self.resource}/{account_id}/reopen", {'reason': reason}) + 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): + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[AccountDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListAccountParams = None) -> Union[UnitResponse[List[AccountDTO]], UnitError]: + params = params or ListAccountParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[AccountDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + 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): + data = response.json().get("data") + return UnitResponse[AccountDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def limits(self, account_id: str) -> Union[UnitResponse[AccountLimitsDTO], UnitError]: + response = super().get(f"{self.resource}/{account_id}/limits", None) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AccountLimitsDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/ach_resource.py b/build/lib/unit/api/ach_resource.py new file mode 100644 index 00000000..f19835db --- /dev/null +++ b/build/lib/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/build/lib/unit/api/api_token_resource.py b/build/lib/unit/api/api_token_resource.py new file mode 100644 index 00000000..e44206d5 --- /dev/null +++ b/build/lib/unit/api/api_token_resource.py @@ -0,0 +1,34 @@ +from unit.api.base_resource import BaseResource +from unit.models.api_token import * +from unit.models.codecs import DtoDecoder + + +class APITokenResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "users" + + def create(self, request: CreateAPITokenRequest) -> Union[UnitResponse[APITokenDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.user_id}/api-tokens", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[APITokenDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, user_id: str) -> Union[UnitResponse[List[APITokenDTO]], UnitError]: + response = super().get(f"{self.resource}/{user_id}/api-tokens") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[APITokenDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def revoke(self, user_id: str, token_id: str) -> Union[UnitResponse, UnitError]: + response = super().delete(f"{self.resource}/{user_id}/api-tokens/{token_id}") + if super().is_20x(response.status_code): + return UnitResponse([], None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/applicationForm_resource.py b/build/lib/unit/api/applicationForm_resource.py new file mode 100644 index 00000000..3ef83f3e --- /dev/null +++ b/build/lib/unit/api/applicationForm_resource.py @@ -0,0 +1,37 @@ +from unit.api.base_resource import BaseResource +from unit.models.applicationForm import * +from unit.models.codecs import DtoDecoder + + +class ApplicationFormResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "application-forms" + + def create(self, request: CreateApplicationFormRequest) -> Union[UnitResponse[ApplicationFormDTO], 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[ApplicationFormDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, application_form_id: str, include: Optional[str] = "") -> Union[UnitResponse[ApplicationFormDTO], UnitError]: + response = super().get(f"{self.resource}/{application_form_id}", {"include": include}) + if super().is_20x(response.status_code): + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[ApplicationFormDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListApplicationFormParams = None) -> Union[UnitResponse[List[ApplicationFormDTO]], UnitError]: + params = params or ListApplicationFormParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[ApplicationFormDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/application_resource.py b/build/lib/unit/api/application_resource.py new file mode 100644 index 00000000..82d041e7 --- /dev/null +++ b/build/lib/unit/api/application_resource.py @@ -0,0 +1,90 @@ +from unit.api.base_resource import BaseResource +from unit.models.application import * +from unit.models.codecs import DtoDecoder + + +class ApplicationResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "applications" + + def create(self, request: Union[CreateIndividualApplicationRequest, CreateBusinessApplicationRequest]) -> Union[UnitResponse[ApplicationDTO], UnitError]: + payload = request.to_json_api() + response = super().post(self.resource, payload) + + if response.ok: + data = response.json().get("data") + included = response.json().get("included") + 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()) + + def list(self, params: ListApplicationParams = None) -> Union[UnitResponse[List[ApplicationDTO]], UnitError]: + params = params or ListApplicationParams() + response = super().get(self.resource, params.to_dict()) + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[ApplicationDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, application_id: str) -> Union[UnitResponse[ApplicationDTO], UnitError]: + response = super().get(f"{self.resource}/{application_id}") + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[ApplicationDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + def upload(self, request: UploadDocumentRequest): + url = f"{self.resource}/{request.application_id}/documents/{request.document_id}" + if request.is_back_side: + url += "/back" + + headers = {} + + if request.file_type == "jpeg": + headers = {"Content-Type": "image/jpeg"} + if request.file_type == "png": + headers = {"Content-Type": "image/png"} + if request.file_type == "pdf": + headers = {"Content-Type": "application/pdf"} + + response = super().put(url, request.file, headers) + if response.status_code == 200: + data = response.json().get("data") + return UnitResponse[ApplicationDocumentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def update(self, request: PatchApplicationRequest) -> Union[UnitResponse[ApplicationDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.application_id}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[ApplicationDTO](DtoDecoder.decode(data), None) + 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/build/lib/unit/api/atmLocation_resource.py b/build/lib/unit/api/atmLocation_resource.py new file mode 100644 index 00000000..5e8b8bab --- /dev/null +++ b/build/lib/unit/api/atmLocation_resource.py @@ -0,0 +1,34 @@ +from unit.api.base_resource import BaseResource +from unit.models.atm_location import * +from unit.models.codecs import DtoDecoder, UnitEncoder + +class AtmLocationResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "atm-locations" + + """ + UnitEncoder must be imported here and not in the model class to no cause circular importing. + """ + def get(self, request: GetAtmLocationParams) -> Union[UnitResponse[List[AtmLocationDTO]], UnitError]: + params = {} + + if request.coordinates: + params["filter[coordinates]"] = json.dumps(request.coordinates, cls=UnitEncoder) + + if request.address: + params["filter[address]"] = json.dumps(request.address, cls=UnitEncoder) + + if request.postal_code: + params["filter[postalCode]"] = json.dumps(request.postal_code, cls=UnitEncoder) + + if request.search_radius: + params["filter[searchRadius]"] = request.search_radius + + response = super().get(self.resource, params) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AtmLocationDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/authorization_request_resource.py b/build/lib/unit/api/authorization_request_resource.py new file mode 100644 index 00000000..56815ee8 --- /dev/null +++ b/build/lib/unit/api/authorization_request_resource.py @@ -0,0 +1,46 @@ +from unit.api.base_resource import BaseResource +from unit.models.authorization_request import * +from unit.models.codecs import DtoDecoder + + +class AuthorizationRequestResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "authorization-requests" + + def get(self, authorization_id: str) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: + response = super().get(f"{self.resource}/{authorization_id}") + 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()) + + def list(self, params: ListPurchaseAuthorizationRequestParams = None) \ + -> Union[UnitResponse[List[PurchaseAuthorizationRequestDTO]], UnitError]: + params = params or ListPurchaseAuthorizationRequestParams() + response = super().get(self.resource, params.to_dict()) + 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()) + + def approve(self, request: ApproveAuthorizationRequest) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.authorization_id}/approve", 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()) + + def decline(self, request: DeclineAuthorizationRequest) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.authorization_id}/decline", 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/build/lib/unit/api/authorization_resource.py b/build/lib/unit/api/authorization_resource.py new file mode 100644 index 00000000..28a3fec7 --- /dev/null +++ b/build/lib/unit/api/authorization_resource.py @@ -0,0 +1,28 @@ +from unit.api.base_resource import BaseResource +from unit.models.authorization import * +from unit.models.codecs import DtoDecoder + + +class AuthorizationResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "authorizations" + + def get(self, authorization_id: str, include_non_authorized: Optional[bool] = False) -> Union[UnitResponse[AuthorizationDTO], UnitError]: + params = {"filter[includeNonAuthorized]": include_non_authorized} + + response = super().get(f"{self.resource}/{authorization_id}", params) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AuthorizationDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListAuthorizationParams = None) -> Union[UnitResponse[List[AuthorizationDTO]], UnitError]: + params = params or ListAuthorizationParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AuthorizationDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/base_resource.py b/build/lib/unit/api/base_resource.py new file mode 100644 index 00000000..b8b06547 --- /dev/null +++ b/build/lib/unit/api/base_resource.py @@ -0,0 +1,44 @@ +import json +from typing import Optional, Dict +import requests +from unit.models.codecs import UnitEncoder + + +class BaseResource(object): + def __init__(self, api_url, token): + self.api_url = api_url + self.token = token + self.headers = { + "content-type": "application/vnd.api+json", + "authorization": f"Bearer {self.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 post(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = 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)) + + def patch(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = 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)) + + 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 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 __merge_headers(self, headers: Optional[Dict[str, str]] = None): + if not headers: + return self.headers + else: + merged = self.headers.copy() + merged.update(**headers) + return merged + + def is_20x(self, status: int): + return status == 200 or status == 201 or status == 204 + diff --git a/build/lib/unit/api/bill_pay_resource.py b/build/lib/unit/api/bill_pay_resource.py new file mode 100644 index 00000000..ee4c7342 --- /dev/null +++ b/build/lib/unit/api/bill_pay_resource.py @@ -0,0 +1,22 @@ +from unit.api.base_resource import BaseResource +from unit.models.bill_pay import * +from unit.models.codecs import DtoDecoder + + +class BillPayResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "payments/billpay/billers" + + def get(self, params: GetBillersParams) -> Union[UnitResponse[List[BillerDTO]], UnitError]: + parameters = {"name": params.name} + if params.page: + parameters["page"] = params.page + + response = super().get(self.resource, parameters) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[BillerDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/card_resource.py b/build/lib/unit/api/card_resource.py new file mode 100644 index 00000000..e51dea54 --- /dev/null +++ b/build/lib/unit/api/card_resource.py @@ -0,0 +1,113 @@ +from unit.api.base_resource import BaseResource +from unit.models.card import * +from unit.models.codecs import DtoDecoder + + +class CardResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "cards" + + def create(self, request: CreateCardRequest) -> Union[UnitResponse[Card], 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[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def report_stolen(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: + response = super().post(f"{self.resource}/{card_id}/report-stolen") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def report_lost(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: + response = super().post(f"{self.resource}/{card_id}/report-lost") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def close(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: + response = super().post(f"{self.resource}/{card_id}/close") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def freeze(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: + response = super().post(f"{self.resource}/{card_id}/freeze") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def unfreeze(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: + response = super().post(f"{self.resource}/{card_id}/unfreeze") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def replace(self, card_id: str, shipping_address: Optional[Address]) -> Union[UnitResponse[Union[IndividualDebitCardDTO, BusinessDebitCardDTO]], UnitError]: + request = ReplaceCardRequest(shipping_address) + payload = request.to_json_api() + response = super().post(f"{self.resource}/{card_id}/replace", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Union[IndividualDebitCardDTO, BusinessDebitCardDTO]](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def update(self, request: PatchCardRequest) -> Union[UnitResponse[Card], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.card_id}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[Card](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, card_id: str, include: Optional[str] = "") -> Union[UnitResponse[Card], UnitError]: + response = super().get(f"{self.resource}/{card_id}", {"include": include}) + if super().is_20x(response.status_code): + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[Card](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListCardParams = None) -> Union[UnitResponse[List[Card]], UnitError]: + params = params or ListCardParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[Card](DtoDecoder.decode(data), DtoDecoder.decode(included)) + else: + return UnitError.from_json_api(response.json()) + + def get_pin_status(self, card_id: str) -> Union[UnitResponse[PinStatusDTO], UnitError]: + response = super().get(f"{self.resource}/{card_id}/secure-data/pin/status") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[PinStatusDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + + def limits(self, card_id: str) -> Union[UnitResponse[CardLimitsDTO], UnitError]: + response = super().get(f"{self.resource}/{card_id}/limits") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CardLimitsDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/counterparty_resource.py b/build/lib/unit/api/counterparty_resource.py new file mode 100644 index 00000000..3cf1518e --- /dev/null +++ b/build/lib/unit/api/counterparty_resource.py @@ -0,0 +1,59 @@ +from unit.api.base_resource import BaseResource +from unit.models.counterparty import * +from unit.models.codecs import DtoDecoder + + +class CounterpartyResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "counterparties" + + def create(self, request: Union[CreateCounterpartyRequest, CreateCounterpartyWithTokenRequest]) -> Union[UnitResponse[CounterpartyDTO], 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[CounterpartyDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def update(self, request: PatchCounterpartyRequest) -> Union[UnitResponse[CounterpartyDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.counterparty_id}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CounterpartyDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def delete(self, counterparty_id: str) -> Union[UnitResponse, UnitError]: + response = super().delete(f"{self.resource}/{counterparty_id}") + if super().is_20x(response.status_code): + return UnitResponse([], None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, counterparty_id: str) -> Union[UnitResponse[CounterpartyDTO], UnitError]: + response = super().get(f"{self.resource}/{counterparty_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CounterpartyDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListCounterpartyParams = None) -> Union[UnitResponse[List[CounterpartyDTO]], UnitError]: + params = params or ListCounterpartyParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CounterpartyDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get_balance(self, counterparty_id: str) -> Union[UnitResponse[CounterpartyBalanceDTO], UnitError]: + response = super().get(f"{self.resource}/{counterparty_id}/balance") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CounterpartyBalanceDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/customerToken_resource.py b/build/lib/unit/api/customerToken_resource.py new file mode 100644 index 00000000..04e2dfd7 --- /dev/null +++ b/build/lib/unit/api/customerToken_resource.py @@ -0,0 +1,29 @@ +from unit.api.base_resource import BaseResource +from unit.models.customerToken import * +from unit.models.codecs import DtoDecoder + + +class CustomerTokenResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "customers" + + def create_token(self, request: CreateCustomerToken) -> Union[UnitResponse[CustomerTokenDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.customer_id}/token", payload) + + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CustomerTokenDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def create_token_verification(self, request: CreateCustomerTokenVerification) -> Union[UnitResponse[CustomerVerificationTokenDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.customer_id}/token/verification", payload) + + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[CustomerVerificationTokenDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/customer_resource.py b/build/lib/unit/api/customer_resource.py new file mode 100644 index 00000000..d164452c --- /dev/null +++ b/build/lib/unit/api/customer_resource.py @@ -0,0 +1,40 @@ +from unit.api.base_resource import BaseResource +from unit.models.customer import * +from unit.models.codecs import DtoDecoder + + +class CustomerResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "customers" + + def update(self, request: Union[PatchIndividualCustomerRequest, PatchBusinessCustomerRequest]) -> Union[UnitResponse[CustomerDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.customer_id}", payload) + + if response.ok: + data = response.json().get("data") + if data["type"] == "individualCustomer": + return UnitResponse[IndividualCustomerDTO](DtoDecoder.decode(data), None) + else: + return UnitResponse[BusinessCustomerDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + + def get(self, customer_id: str) -> Union[UnitResponse[CustomerDTO], UnitError]: + response = super().get(f"{self.resource}/{customer_id}") + if response.status_code == 200: + data = response.json().get("data") + return UnitResponse[CustomerDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListCustomerParams = None) -> Union[UnitResponse[List[CustomerDTO]], UnitError]: + params = params or ListCustomerParams() + response = super().get(self.resource, params.to_dict()) + if response.status_code == 200: + data = response.json().get("data") + return UnitResponse[CustomerDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/event_resource.py b/build/lib/unit/api/event_resource.py new file mode 100644 index 00000000..aaec2f97 --- /dev/null +++ b/build/lib/unit/api/event_resource.py @@ -0,0 +1,33 @@ +from unit.api.base_resource import BaseResource +from unit.models.event import * +from unit.models.codecs import DtoDecoder + + +class EventResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "events" + + def get(self, event_id: str) -> Union[UnitResponse[EventDTO], UnitError]: + response = super().get(f"{self.resource}/{event_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[EventDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListEventParams = None) -> Union[UnitResponse[List[EventDTO]], UnitError]: + params = params or ListEventParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[EventDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def fire(self, event_id: str) -> Union[UnitResponse, UnitError]: + response = super().post(f"{self.resource}/{event_id}") + if super().is_20x(response.status_code): + return UnitResponse([], None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/fee_resource.py b/build/lib/unit/api/fee_resource.py new file mode 100644 index 00000000..68bb2756 --- /dev/null +++ b/build/lib/unit/api/fee_resource.py @@ -0,0 +1,19 @@ +from unit.api.base_resource import BaseResource +from unit.models.fee import * +from unit.models.codecs import DtoDecoder + + +class FeeResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "fees" + + def create(self, request: CreateFeeRequest) -> Union[UnitResponse[FeeDTO], 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[FeeDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/institution_resource.py b/build/lib/unit/api/institution_resource.py new file mode 100644 index 00000000..d91ce96d --- /dev/null +++ b/build/lib/unit/api/institution_resource.py @@ -0,0 +1,17 @@ +from unit.api.base_resource import BaseResource +from unit.models.institution import * +from unit.models.codecs import DtoDecoder + + +class InstitutionResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "institutions" + + def get(self, routing_number: str) -> Union[UnitResponse[InstitutionDTO], UnitError]: + response = super().get(f"{self.resource}/{routing_number}", None) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[InstitutionDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/payment_resource.py b/build/lib/unit/api/payment_resource.py new file mode 100644 index 00000000..33c13fda --- /dev/null +++ b/build/lib/unit/api/payment_resource.py @@ -0,0 +1,57 @@ +from unit.api.base_resource import BaseResource +from unit.models.payment import * +from unit.models.codecs import DtoDecoder, split_json_api_single_response + + +class PaymentResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "payments" + + def create(self, request: CreatePaymentRequest) -> Union[UnitResponse[PaymentDTO], 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[PaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def update(self, request: PatchPaymentRequest) -> Union[UnitResponse[PaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.payment_id}", payload) + 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()) + + def get(self, payment_id: str, include: Optional[str] = "") -> Union[UnitResponse[PaymentDTO], UnitError]: + response = super().get(f"{self.resource}/{payment_id}", {"include": include}) + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[PaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListPaymentParams = None) -> Union[UnitResponse[List[PaymentDTO]], UnitError]: + params = params or ListPaymentParams() + response = super().get(self.resource, params.to_dict()) + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[PaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) + 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()) diff --git a/build/lib/unit/api/received_payment_resource.py b/build/lib/unit/api/received_payment_resource.py new file mode 100644 index 00000000..b453d6ac --- /dev/null +++ b/build/lib/unit/api/received_payment_resource.py @@ -0,0 +1,45 @@ +from unit.api.base_resource import BaseResource +from unit.models.payment import * +from unit.models.codecs import DtoDecoder + + +class ReceivedPaymentResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "received-payments" + + def update(self, request: PatchPaymentRequest) -> Union[UnitResponse[AchReceivedPaymentDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.payment_id}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, payment_id: str, include: Optional[str] = "") -> Union[UnitResponse[AchReceivedPaymentDTO], UnitError]: + response = super().get(f"{self.resource}/{payment_id}", {"include": include}) + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListReceivedPaymentParams = None) -> Union[UnitResponse[List[AchReceivedPaymentDTO]], UnitError]: + params = params or ListReceivedPaymentParams() + response = super().get(self.resource, params.to_dict()) + if response.status_code == 200: + data = response.json().get("data") + included = response.json().get("included") + return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) + else: + return UnitError.from_json_api(response.json()) + + 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()) \ No newline at end of file diff --git a/build/lib/unit/api/repayment_resource.py b/build/lib/unit/api/repayment_resource.py new file mode 100644 index 00000000..d55a69d0 --- /dev/null +++ b/build/lib/unit/api/repayment_resource.py @@ -0,0 +1,38 @@ +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_create(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/build/lib/unit/api/returnAch_resource.py b/build/lib/unit/api/returnAch_resource.py new file mode 100644 index 00000000..948479de --- /dev/null +++ b/build/lib/unit/api/returnAch_resource.py @@ -0,0 +1,20 @@ +from unit.api.base_resource import BaseResource +from unit.models.transaction import ReturnedReceivedAchTransactionDTO +from unit.models.returnAch import * +from unit.models.codecs import DtoDecoder + + +class ReturnAchResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "returns" + + def return_ach(self, request: ReturnReceivedAchTransactionRequest) -> Union[UnitResponse[ReturnedReceivedAchTransactionDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/{request.transaction_id}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[ReturnedReceivedAchTransactionDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/reward_resource.py b/build/lib/unit/api/reward_resource.py new file mode 100644 index 00000000..8cafa463 --- /dev/null +++ b/build/lib/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/build/lib/unit/api/statement_resource.py b/build/lib/unit/api/statement_resource.py new file mode 100644 index 00000000..be711e1f --- /dev/null +++ b/build/lib/unit/api/statement_resource.py @@ -0,0 +1,38 @@ +from unit.api.base_resource import BaseResource +from unit.models.statement import * +from unit.models.codecs import DtoDecoder + + +class StatementResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "statements" + + def get(self, params: GetStatementParams) -> Union[UnitResponse[str], UnitError]: + parameters = {"language": params.language} + if params.customer_id: + parameters["filter[customerId]"] = params.customer_id + + response = super().get(f"{self.resource}/{params.statement_id}/{params.output_type}", parameters) + if response.status_code == 200: + return UnitResponse[bytes](response.content, None) + else: + return UnitError.from_json_api(response.json()) + + def get_bank_verification(self, account_id: str, include_proof_of_funds: Optional[bool] = False) -> Union[UnitResponse[str], UnitError]: + 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) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListStatementParams = None) -> Union[UnitResponse[List[StatementDTO]], UnitError]: + params = params or ListStatementParams() + response = super().get(self.resource, params.to_dict()) + if response.status_code == 200: + data = response.json().get("data") + return UnitResponse[StatementDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + diff --git a/build/lib/unit/api/transaction_resource.py b/build/lib/unit/api/transaction_resource.py new file mode 100644 index 00000000..abcb8e38 --- /dev/null +++ b/build/lib/unit/api/transaction_resource.py @@ -0,0 +1,41 @@ +from unit.api.base_resource import BaseResource +from unit.models.transaction import * +from unit.models.codecs import DtoDecoder +from unit.models.transaction import * + + +class TransactionResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "transactions" + + 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") + 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[TransactionDTO]], UnitError]: + params = params or ListTransactionParams() + response = super().get(self.resource, params.to_dict()) + 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 update(self, request: PatchTransactionRequest) -> Union[UnitResponse[TransactionDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"accounts/{request.account_id}/{self.resource}/{request.transaction_id}", payload) + 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()) + diff --git a/build/lib/unit/api/webhook_resource.py b/build/lib/unit/api/webhook_resource.py new file mode 100644 index 00000000..78349feb --- /dev/null +++ b/build/lib/unit/api/webhook_resource.py @@ -0,0 +1,72 @@ +from unit.api.base_resource import BaseResource +from unit.models.webhook import * +from unit.models.codecs import DtoDecoder +import hmac +from hashlib import sha1 +import base64 + + +class WebhookResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "webhooks" + + def create(self, request: CreateWebhookRequest) -> Union[UnitResponse[WebhookDTO], 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[WebhookDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def get(self, webhook_id: str) -> Union[UnitResponse[WebhookDTO], UnitError]: + response = super().get(f"{self.resource}/{webhook_id}") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def list(self, params: ListWebhookParams = None) -> Union[UnitResponse[List[WebhookDTO]], UnitError]: + params = params or ListWebhookParams() + response = super().get(self.resource, params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def update(self, request: PatchWebhookRequest) -> Union[UnitResponse[WebhookDTO], UnitError]: + payload = request.to_json_api() + response = super().patch(f"{self.resource}/{request.webhook_id}", payload) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def enable(self, webhook_id: str) -> Union[UnitResponse[WebhookDTO], UnitError]: + response = super().post(f"{self.resource}/{webhook_id}/enable") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def disable(self, webhook_id: str) -> Union[UnitResponse[WebhookDTO], UnitError]: + response = super().post(f"{self.resource}/{webhook_id}/disable") + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) + + def verify(self, signature: str, secret: str, payload): + mac = hmac.new( + secret.encode(), + msg=json.dumps(payload, separators=(',', ':'), ensure_ascii=False).encode('utf-8'), + digestmod=sha1, + ) + res = base64.encodebytes(mac.digest()).decode().rstrip('\n') + return res == signature diff --git a/build/lib/unit/models/__init__.py b/build/lib/unit/models/__init__.py new file mode 100644 index 00000000..a25d06dc --- /dev/null +++ b/build/lib/unit/models/__init__.py @@ -0,0 +1,301 @@ +import json +from typing import TypeVar, Generic, Union, Optional, Literal, List, Dict +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 + self.id = _id + + def to_dict(self): + return {"type": self.type, "id": self.id} + + +T = TypeVar('T') + +class RelationshipArray(Generic[T]): + def __init__(self, l: List[T]): + self.relationships = l + + +class UnitResponse(Generic[T]): + def __init__(self, data: Union[T, List[T]], included): + self.data = data + self.included = included + + @staticmethod + def from_json_api(data: str): + pass + + +class UnitRequest(object): + def to_json_api(self) -> Dict: + pass + +class UnitParams(object): + def to_dict(self) -> Dict: + pass + +class RawUnitObject(object): + def __init__(self, _id, _type, attributes, relationships): + self.id = _id + self.type = _type + 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): + self.title = title + self.status = status + self.detail = detail + self.details = details + self.source = source + + def __str__(self): + return self.detail + + +class UnitError(object): + def __init__(self, errors: List[UnitErrorPayload]): + self.errors = errors + + @staticmethod + 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)) + ) + + 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]}) + + +Status = Literal["Approved", "Denied", "PendingReview"] +Title = Literal["CEO", "COO", "CFO", "President"] +EntityType = Literal["Corporation", "LLC", "Partnership"] + +class FullName(object): + 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): + self.street = street + self.street2 = street2 + self.city = city + self.state = state + self.postal_code = postal_code + self.country = 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)) + + +class Phone(object): + def __init__(self, country_code: str, number: str): + self.country_code = country_code + self.number = number + + @staticmethod + def from_json_api(data: Dict): + return Phone(data.get("countryCode"), data.get("number")) + + +class BusinessContact(object): + def __init__(self, full_name: FullName, email: str, phone: Phone): + self.full_name = full_name + self.email = email + self.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"))) + + +class Officer(object): + 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): + 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.title = title + self.ssn = ssn + self.passport = passport + self.nationality = nationality + + @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): + 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(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"))) + return beneficial_owners + + +class AuthorizedUser(object): + def __init__(self, full_name: FullName, email: str, phone: Phone): + self.full_name = full_name + self.email = email + self.phone = phone + + @staticmethod + 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"))) + return authorized_users + +class WireCounterparty(object): + 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 + self.address = 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): + self.routing_number = routing_number + self.account_number = account_number + self.account_type = account_type + self.name = name + + @staticmethod + def from_json_api(data: Dict): + return Counterparty(data["routingNumber"], data["accountNumber"], data["accountType"], data["name"]) + +class Coordinates(object): + def __init__(self, longitude: int, latitude: int): + self.longitude = longitude + self.latitude = latitude + + @staticmethod + def from_json_api(data: Dict): + if data: + return Coordinates(data["longitude"], data["latitude"]) + else: + return None + + +class Merchant(object): + def __init__(self, name: str, type: int, category: Optional[str], location: Optional[str]): + self.name = name + self.type = type + self.category = category + self.location = location + + @staticmethod + def from_json_api(data: Dict): + return Merchant(data["name"], data["type"], data.get("category"), data.get("location")) + +class CardLevelLimits(object): + 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 + self.monthly_purchase = monthly_purchase + + @staticmethod + def from_json_api(data: Dict): + return CardLevelLimits(data["dailyWithdrawal"], data["dailyPurchase"], data["monthlyWithdrawal"], + data["monthlyPurchase"]) + +class CardTotals(object): + def __init__(self, withdrawals: int, deposits: int, purchases: int): + self.withdrawals = withdrawals + self.deposits = deposits + self.purchases = purchases + + @staticmethod + def from_json_api(data: Dict): + return CardTotals(data["withdrawals"], data["deposits"], data["purchases"]) + + +class DeviceFingerprint(object): + def __init__(self, value: str, provider: str = "iovation"): + self.value = value + self.provider = provider + + def to_json_api(self): + return { + "value": self.value, + "provider": self.provider, + } + + @classmethod + def from_json_api(cls, data: Dict): + return cls(value=data["value"], provider=data["provider"]) diff --git a/build/lib/unit/models/account.py b/build/lib/unit/models/account.py new file mode 100644 index 00000000..70dcc444 --- /dev/null +++ b/build/lib/unit/models/account.py @@ -0,0 +1,344 @@ +from unit.utils import date_utils + +from unit.models import * + +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, + account_number: str, currency: str, balance: int, hold: int, available: int, status: AccountStatus, + tags: Optional[Dict[str, str]], close_reason: Optional[CloseReason], + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "depositAccount" + self.attributes = {"name": name, "createdAt": created_at, "depositProduct": deposit_product, + "routingNumber": routing_number, "accountNumber": account_number, "currency": currency, + "balance": balance, "hold": hold, "available": available, "status": status, + "closeReason": close_reason, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DepositAccountDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["name"], attributes["depositProduct"], + attributes["routingNumber"], attributes["accountNumber"], attributes["currency"], attributes["balance"], + attributes["hold"], attributes["available"], attributes["status"], attributes.get("tags"), + attributes.get("closeReason"), relationships + ) + + +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): + def __init__(self, deposit_product: str, relationships: Optional[Dict[str, Union[Relationship, RelationshipArray]]], + tags: Optional[Dict[str, str]] = None, idempotency_key: Optional[str] = None): + self.deposit_product = deposit_product + self.tags = tags + self.idempotency_key = idempotency_key + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "depositAccount", + "attributes": { + "depositProduct": self.deposit_product, + }, + "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): + 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): + self.account_id = account_id + self.deposit_product = deposit_product + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "depositAccount", + "attributes": {} + } + } + + if self.deposit_product: + payload["data"]["attributes"]["depositProduct"] = self.deposit_product + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + 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): + self.debits = debits + self.credits = credits + + @staticmethod + def from_json_api(data: Dict): + return AchTotals(data["debits"], data["credits"]) + + +class AchLimits(object): + def __init__(self, daily_debit: int, daily_credit: int, monthly_debit: int, monthly_credit: int, + daily_debit_soft: int, monthly_debit_soft: int): + self.daily_debit = daily_debit + self.daily_credit = daily_credit + self.monthly_debit = monthly_debit + self.monthly_credit = monthly_credit + self.daily_debit_soft = daily_debit_soft + self.monthly_debit_soft = monthly_debit_soft + + @staticmethod + def from_json_api(data: Dict): + return AchLimits(data["dailyDebit"], data["dailyCredit"], data["monthlyDebit"], data["monthlyCredit"], + data["dailyDebitSoft"], data["monthlyDebitSoft"]) + + +class AccountAchLimits(object): + def __init__(self, limits: AchLimits, totals_daily: AchTotals, totals_monthly: AchTotals): + self.limits = limits + self.totals_daily = totals_daily + self.totals_monthly = totals_monthly + + @staticmethod + def from_json_api(data: Dict): + return AccountAchLimits(AchLimits.from_json_api(data["limits"]), AchTotals.from_json_api(data["totalsDaily"]), + AchTotals.from_json_api(data["totalsMonthly"])) + + +class CardLimits(object): + def __init__(self, daily_withdrawal: int, daily_deposit: int, daily_purchase: int, daily_card_transaction: int): + self.daily_withdrawal = daily_withdrawal + self.daily_deposit = daily_deposit + self.daily_purchase = daily_purchase + self.daily_card_transaction = daily_card_transaction + + @staticmethod + def from_json_api(data: Dict): + return CardLimits(data["dailyWithdrawal"], data["dailyDeposit"], + data["dailyPurchase"], data["dailyCardTransaction"]) + +class CardTotals(object): + def __init__(self, withdrawals: int, deposits: int, purchases: int, card_transactions: int): + self.withdrawals = withdrawals + self.deposits = deposits + self.purchases = purchases + self.card_transactions = card_transactions + + @staticmethod + def from_json_api(data: Dict): + return CardTotals(data["withdrawals"], data["deposits"], data["purchases"], data["cardTransactions"]) + +class AccountCardLimits(object): + def __init__(self, limits: CardLimits, totals_daily: CardTotals): + self.limits = limits + self.totals_daily = totals_daily + + @staticmethod + def from_json_api(data: Dict): + return AccountCardLimits(CardLimits.from_json_api(data["limits"]), + CardTotals.from_json_api(data["totalsDaily"])) + + +class CheckDepositLimits(object): + def __init__(self, daily: int, monthly: int, daily_soft: int, monthly_soft: int): + self.daily = daily + self.monthly = monthly + self.daily_soft = daily_soft + self.monthly_soft = monthly_soft + + @staticmethod + def from_json_api(data: Dict): + return CheckDepositLimits(data["daily"], data["monthly"], data["dailySoft"], data["monthlySoft"]) + + +class CheckDepositAccountLimits(object): + def __init__(self, limits: CheckDepositLimits, totals_daily: int, totals_monthly: int): + self.limits = limits + self.totals_daily = totals_daily + self.totals_monthly = totals_monthly + + @staticmethod + def from_json_api(data: Dict): + return CheckDepositAccountLimits(CheckDepositLimits.from_json_api(data["limits"]), data["totalsDaily"], + data["totalsMonthly"]) + + +class AccountLimitsDTO(object): + def __init__(self, ach: AccountAchLimits, card: AccountCardLimits, check_deposit: CheckDepositAccountLimits): + self.type = "limits" + self.attributes = {"ach": ach, "card": card, "checkDeposit": check_deposit} + + @staticmethod + def from_json_api(attributes): + return AccountLimitsDTO(AccountAchLimits.from_json_api(attributes["ach"]), + AccountCardLimits.from_json_api(attributes["card"]), + CheckDepositAccountLimits.from_json_api(attributes["checkDeposit"])) + + +class CloseAccountRequest(UnitRequest): + def __init__(self, account_id: str, reason: Optional[Literal["ByCustomer", "Fraud"]] = "ByCustomer"): + self.account_id = account_id + self.reason = reason + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "accountClose", + "attributes": { + "reason": self.reason, + } + } + } + + return payload + + def __repr__(self): + 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[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]"] = 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/build/lib/unit/models/account_end_of_day.py b/build/lib/unit/models/account_end_of_day.py new file mode 100644 index 00000000..b22bf22a --- /dev/null +++ b/build/lib/unit/models/account_end_of_day.py @@ -0,0 +1,41 @@ +import json +from typing import Optional +from unit.models import * + + +class AccountEndOfDayDTO(object): + def __init__(self, id: str, date: str, balance: int, hold: int, available: int, + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "accountEndOfDay" + self.attributes = {"date": date, "balance": balance, "hold": hold, "available": available} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AccountEndOfDayDTO(_id, attributes["date"], attributes["balance"], attributes["hold"], + attributes["available"], relationships) + + +class ListAccountEndOfDayParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, + customer_id: Optional[str] = None, since: Optional[str] = None, until: Optional[str] = None): + self.limit = limit + self.offset = offset + self.account_id = account_id + self.customer_id = customer_id + self.since = since + self.until = until + + 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.account_id: + parameters["filter[accountId]"] = self.account_id + if self.since: + parameters["filter[since]"] = self.since + if self.until: + parameters["filter[until]"] = self.until + return parameters + diff --git a/build/lib/unit/models/api_token.py b/build/lib/unit/models/api_token.py new file mode 100644 index 00000000..a08e3550 --- /dev/null +++ b/build/lib/unit/models/api_token.py @@ -0,0 +1,48 @@ +from unit.models import * +from unit.utils import date_utils + + +class APITokenDTO(object): + def __init__(self, id: str, created_at: datetime, description: str, expiration: datetime, token: Optional[str], + source_ip: Optional[str]): + self.id = id + self.type = "apiToken" + self.attributes = {"createdAt": created_at, "description": description, "expiration": expiration, + "token": token, "sourceIp": source_ip} + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return APITokenDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["description"], + date_utils.to_datetime(attributes["expiration"]), attributes.get("token"), + attributes.get("sourceIp")) + + +class CreateAPITokenRequest(object): + def __init__(self, user_id: str, description: str, scope: str, expiration: datetime, + source_ip: Optional[str] = None): + self.user_id = user_id + self.description = description + self.scope = scope + self.expiration = expiration + self.source_ip = source_ip + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "apiToken", + "attributes": { + "description": self.description, + "scope": self.scope, + "expiration": self.expiration + } + } + } + + if self.source_ip: + payload["data"]["attributes"]["sourceIp"] = self.source_ip + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + diff --git a/build/lib/unit/models/application.py b/build/lib/unit/models/application.py new file mode 100644 index 00000000..bfb450b7 --- /dev/null +++ b/build/lib/unit/models/application.py @@ -0,0 +1,564 @@ +from unit.utils import date_utils +from unit.models import * +from typing import IO + +ApplicationStatus = Literal["Approved", "Denied", "Pending", "PendingReview"] + +DocumentType = Literal[ + "IdDocument", + "Passport", + "AddressVerification", + "CertificateOfIncorporation", + "EmployerIdentificationNumberConfirmation", +] + +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" +] + + +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]], + ): + 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.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"]), + 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, + ) + + +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]], + ): + 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.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, + ) + + +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, + ): + self.full_name = full_name + self.date_of_birth = date_of_birth + self.address = address + self.email = email + self.phone = phone + self.ip = ip + self.ein = ein + self.dba = dba + self.sole_proprietorship = sole_proprietorship + self.ssn = ssn + self.passport = passport + self.nationality = nationality + self.device_fingerprints = device_fingerprints + self.idempotency_key = idempotency_key + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "individualApplication", + "attributes": { + "fullName": self.full_name, + "dateOfBirth": date_utils.to_date_str(self.date_of_birth), + "address": self.address, + "email": self.email, + "phone": self.phone, + }, + } + } + + if self.ip: + payload["data"]["attributes"]["ip"] = self.ip + + if self.ein: + payload["data"]["attributes"]["ein"] = self.ein + + if self.dba: + payload["data"]["attributes"]["dba"] = self.dba + + if self.sole_proprietorship: + payload["data"]["attributes"][ + "soleProprietorship" + ] = self.sole_proprietorship + + if self.ssn: + payload["data"]["attributes"]["ssn"] = self.ssn + + if self.passport: + payload["data"]["attributes"]["passport"] = self.passport + + if self.nationality: + payload["data"]["attributes"]["nationality"] = self.nationality + + if self.idempotency_key: + 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 + ] + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +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, + 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 + ): + self.name = name + self.address = address + self.phone = phone + self.state_of_incorporation = state_of_incorporation + self.ein = ein + self.contact = contact + self.officer = officer + self.beneficial_owners = beneficial_owners + self.entity_type = entity_type + 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 + + def to_json_api(self) -> dict: + payload = { + "data": { + "type": "businessApplication", + "attributes": { + "name": self.name, + "address": self.address, + "phone": self.phone, + "stateOfIncorporation": self.state_of_incorporation, + "ein": self.ein, + "contact": self.contact, + "officer": self.officer, + "beneficialOwners": self.beneficial_owners, + "entityType": self.entity_type, + "industry": self.industry, + "annualRevenue": self.annual_revenue, + "numberOfEmployees": self.number_of_employees, + "cashFlow": self.cash_flow, + "yearOfIncorporation": self.year_of_incorporation, + "countriesOfOperation": self.countries_of_operation, + "stockSymbol": self.stock_symbol, + "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 + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +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], + ): + 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, + } + + @staticmethod + def from_json_api(_id, _type, attributes): + 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"), + ) + + +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, + ): + self.application_id = application_id + self.document_id = document_id + self.file = file + self.file_type = file_type + self.is_back_side = is_back_side + + +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, + ): + self.offset = offset + self.limit = limit + self.email = email + self.query = query + self.sort = sort + self.tags = tags + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + if self.email: + parameters["filter[email]"] = self.email + if self.query: + parameters["filter[query]"] = self.query + if self.tags: + parameters["filter[tags]"] = self.tags + if self.sort: + parameters["sort"] = self.sort + return parameters + + +class PatchApplicationRequest(UnitRequest): + 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": {}}} + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + 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/build/lib/unit/models/applicationForm.py b/build/lib/unit/models/applicationForm.py new file mode 100644 index 00000000..99708e7a --- /dev/null +++ b/build/lib/unit/models/applicationForm.py @@ -0,0 +1,103 @@ +import json +from datetime import datetime, date +from typing import Literal, Optional +from unit.utils import date_utils +from unit.models import * + +ApplicationFormStage = Literal["ChooseBusinessOrIndividual", "EnterIndividualInformation", + "IndividualApplicationCreated", "EnterBusinessInformation", "EnterOfficerInformation", + "EnterBeneficialOwnersInformation", "BusinessApplicationCreated", + "EnterSoleProprietorshipInformation", "SoleProprietorshipApplicationCreated"] + + +class ApplicationFormPrefill(object): + def __init__(self, application_type: Optional[str], full_name: Optional[FullName], ssn: Optional[str], + passport: Optional[str], nationality: Optional[str], date_of_birth: Optional[date], + email: Optional[str], name: Optional[str], state_of_incorporation: Optional[str], + entity_type: Optional[str], contact: Optional[BusinessContact], officer: Optional[Officer], + beneficial_owners: [BeneficialOwner], website: Optional[str], dba: Optional[str], + ein: Optional[str], address: Optional[Address], phone: Optional[Phone]): + self.application_type = application_type + self.full_name = full_name + self.ssn = ssn + self.passport = passport + self.nationality = nationality + self.date_of_birth = date_of_birth + self.email = email + self.name = name + self.state_of_incorporation = state_of_incorporation + self.entity_type = entity_type + self.contact = contact + self.officer = officer + self.beneficial_owners = beneficial_owners + self.website = website + self.dba = dba + self.ein = ein + self.address = address + self.phone = phone + + +class ApplicationFormDTO(object): + def __init__(self, id: str, url: str, stage: ApplicationFormStage, applicant_details: ApplicationFormPrefill, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "applicationForm" + self.attributes = {"url": url, "stage": stage, "applicantDetails": applicant_details, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ApplicationFormDTO(_id, attributes["url"], attributes["stage"], attributes.get("applicantDetails"), + attributes.get("tags"), relationships) + + +AllowedApplicationTypes = Union["Individual", "Business", "SoleProprietorship"] + + +class CreateApplicationFormRequest(UnitRequest): + def __init__(self, tags: Optional[Dict[str, str]] = None, + application_details: Optional[ApplicationFormPrefill] = None, + allowed_application_types: [AllowedApplicationTypes] = None): + self.tags = tags + self.application_details = application_details + self.allowed_application_types = allowed_application_types + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "applicationForm", + "attributes": {} + } + } + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + if self.application_details: + payload["data"]["attributes"]["applicantDetails"] = self.application_details + + if self.allowed_application_types: + payload["data"]["attributes"]["allowedApplicationTypes"] = self.allowed_application_types + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class ListApplicationFormParams(UnitParams): + def __init__(self, offset: int = 0, limit: int = 100, tags: Optional[object] = None, + sort: Optional[Literal["createdAt", "-createdAt"]] = None): + self.offset = offset + self.limit = limit + self.tags = tags + self.sort = sort + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + if self.tags: + parameters["filter[tags]"] = self.tags + if self.sort: + parameters["sort"] = self.sort + return parameters + diff --git a/build/lib/unit/models/atm_location.py b/build/lib/unit/models/atm_location.py new file mode 100644 index 00000000..95d2a545 --- /dev/null +++ b/build/lib/unit/models/atm_location.py @@ -0,0 +1,28 @@ +import json +from unit.models import * + + +class AtmLocationDTO(object): + def __init__(self, network: int, location_name: str, coordinates: Coordinates, address: Address, distance: int, + surcharge_free: bool, accept_deposits: bool): + self.type = "atmLocation" + self.attributes = {"network": network, "locationName": location_name, "coordinates": coordinates, + "address": address, "distance": distance, "surchargeFree": surcharge_free, + "acceptDeposits": accept_deposits} + + @staticmethod + def from_json_api(_type, attributes): + return AtmLocationDTO(attributes["network"], attributes["locationName"], + Coordinates.from_json_api(attributes["coordinates"]), + Address.from_json_api(attributes["address"]), attributes["distance"], + attributes["surchargeFree"], attributes["acceptDeposits"]) + + +class GetAtmLocationParams(object): + def __init__(self, search_radius: Optional[int] = None, coordinates: Optional[Coordinates] = None, + postal_code: Optional[str] = None, address: Optional[Address] = None): + self.search_radius = search_radius + self.coordinates = coordinates + self.postal_code = postal_code + self.address = address + diff --git a/build/lib/unit/models/authorization.py b/build/lib/unit/models/authorization.py new file mode 100644 index 00000000..9f6de45b --- /dev/null +++ b/build/lib/unit/models/authorization.py @@ -0,0 +1,61 @@ +import json +from typing import Optional +from unit.models import * +from unit.utils import date_utils + +AuthorizationStatus = Literal["Authorized", "Completed", "Canceled", "Declined"] + +class AuthorizationDTO(object): + def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, status: AuthorizationStatus, + 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": 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"], + Merchant.from_json_api(attributes["merchant"]), attributes["recurring"], + attributes.get("tags"), relationships) + + +class ListAuthorizationParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, + customer_id: Optional[str] = None, card_id: Optional[str] = None, since: Optional[str] = None, + until: Optional[str] = None, include_non_authorized: Optional[bool] = False, + status: Optional[str] = None, sort: Optional[Literal["createdAt", "-createdAt"]] = None): + self.limit = limit + self.offset = offset + self.account_id = account_id + self.customer_id = customer_id + self.card_id = card_id + self.since = since + self.until = until + self.include_non_authorized = include_non_authorized + self.status = status + self.sort = sort + + 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.account_id: + parameters["filter[accountId]"] = self.account_id + if self.card_id: + parameters["filter[cardId]"] = self.card_id + if self.include_non_authorized: + parameters["filter[includeNonAuthorized]"] = self.include_non_authorized + if self.status: + parameters["filter[status]"] = self.status + if self.since: + parameters["filter[since]"] = self.since + if self.until: + parameters["filter[until]"] = self.until + if self.sort: + parameters["sort"] = self.sort + return parameters diff --git a/build/lib/unit/models/authorization_request.py b/build/lib/unit/models/authorization_request.py new file mode 100644 index 00000000..f654701d --- /dev/null +++ b/build/lib/unit/models/authorization_request.py @@ -0,0 +1,98 @@ +import json +from typing import Optional, Literal +from unit.models import * +from unit.utils import date_utils + +PurchaseAuthorizationRequestStatus = Literal["Pending", "Approved", "Declined"] +DeclineReason = Literal["AccountClosed", "CardExceedsAmountLimit", "DoNotHonor", "InsufficientFunds", "InvalidMerchant", + "ReferToCardIssuer", "RestrictedCard", "Timeout", "TransactionNotPermittedToCardholder"] + +class PurchaseAuthorizationRequestDTO(object): + def __init__(self, id: str, created_at: datetime, amount: int, status: PurchaseAuthorizationRequestStatus, + partial_approval_allowed: str, approved_amount: Optional[int], decline_reason: Optional[DeclineReason], + 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]]): + self.id = id + self.type = "purchaseAuthorizationRequest" + self.attributes = {"createdAt": created_at, "amount": amount, "status": status, + "partialApprovalAllowed": partial_approval_allowed, "approvedAmount": approved_amount, + "declineReason": decline_reason, "merchant": { "name": merchant_name, "type": merchant_type, + "category": merchant_category, + "location": merchant_location}, + "recurring": recurring, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PurchaseAuthorizationRequestDTO(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["amount"], attributes["status"], + attributes.get("partialApprovalAllowed"), + attributes.get("approvedAmount"), attributes.get("declineReason"), + attributes["merchant"]["name"], attributes["merchant"]["type"], + attributes["merchant"]["category"], + attributes["merchant"].get("location"), attributes["recurring"], + attributes.get("tags"), relationships) + + +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 + self.offset = offset + self.account_id = account_id + self.customer_id = customer_id + + 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.account_id: + parameters["filter[accountId]"] = self.account_id + return parameters + + +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 + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "approveAuthorizationRequest", + "attributes": {} + } + } + + if self.amount: + payload["data"]["attributes"]["amount"] = self.amount + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class DeclineAuthorizationRequest(UnitRequest): + def __init__(self, authorization_id: str, reason: DeclineReason): + self.authorization_id = authorization_id + self.reason = reason + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "declineAuthorizationRequest", + "attributes": { + "reason": self.reason + } + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) diff --git a/build/lib/unit/models/benificial_owner.py b/build/lib/unit/models/benificial_owner.py new file mode 100644 index 00000000..3b3fcd12 --- /dev/null +++ b/build/lib/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/build/lib/unit/models/bill_pay.py b/build/lib/unit/models/bill_pay.py new file mode 100644 index 00000000..23e25713 --- /dev/null +++ b/build/lib/unit/models/bill_pay.py @@ -0,0 +1,21 @@ +import json +from typing import Optional +from unit.models import * + + +class BillerDTO(object): + def __init__(self, id: str, name: int, category: str): + self.id = id + self.type = "biller" + self.attributes = {"name": name, "category": category} + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BillerDTO(_id, attributes["name"], attributes["category"]) + + +class GetBillersParams(object): + def __init__(self, name: str, page: Optional[int] = None): + self.name = name + self.page = page + diff --git a/build/lib/unit/models/card.py b/build/lib/unit/models/card.py new file mode 100644 index 00000000..bf0c4ab0 --- /dev/null +++ b/build/lib/unit/models/card.py @@ -0,0 +1,617 @@ +from unit.utils import date_utils +from unit.models import * + +CardStatus = Literal["Inactive", "Active", "Stolen", "Lost", "Frozen", "ClosedByCustomer", "SuspectedFraud"] + + +class IndividualDebitCardDTO(object): + def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration_date: str, status: CardStatus, + shipping_address: Optional[Address], design: Optional[str], + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "individualDebitCard" + self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, + "status": status, "shippingAddress": shipping_address, "design": design} + 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 IndividualDebitCardDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], + attributes["expirationDate"], attributes["status"], + shipping_address, attributes.get("design"), relationships + ) + + +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]], 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, + "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + 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"), + 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, + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "individualVirtualDebitCard" + self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, + "status": status} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return IndividualVirtualDebitCardDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], + attributes["expirationDate"], attributes["status"], relationships + ) + + +class BusinessVirtualDebitCardDTO(object): + 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] = 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, + "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, + "phone": phone, "email": email, "status": status, "passport": passport, + "nationality": nationality} + 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"], 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")) + +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)) + +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 + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "individualDebitCard", + "attributes": {}, + "relationships": self.relationships + } + } + + 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 + + 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 CreateBusinessCard(object): + def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, + 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.shipping_address = shipping_address + self.ssn = ssn + self.passport = passport + self.nationality = nationality + self.design = design + 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, _type: str) -> Dict: + payload = { + "data": { + "type": _type, + "attributes": { + "fullName": self.full_name, + "dateOfBirth": self.date_of_birth, + "address": self.address, + "phone": self.phone, + "email": self.email, + }, + "relationships": self.relationships + } + } + + if self.shipping_address: + payload["data"]["attributes"]["shippingAddress"] = self.shipping_address + + if self.ssn: + payload["data"]["attributes"]["ssn"] = self.ssn + + if self.passport: + payload["data"]["attributes"]["passport"] = self.passport + + if self.nationality: + payload["data"]["attributes"]["nationality"] = self.nationality + + if self.design: + payload["data"]["attributes"]["design"] = self.design + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + + 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): + return json.dumps(self.to_json_api()) + + +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, + limits: Optional[CardLevelLimits] = None, tags: Optional[Dict[str, str]] = None): + self.idempotency_key = idempotency_key + self.limits = limits + self.tags = tags + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "individualVirtualDebitCard", + "attributes": {}, + "relationships": self.relationships + } + } + + 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 + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class CreateBusinessVirtualCard(object): + def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, + 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.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, _type: str) -> Dict: + payload = { + "data": { + "type": _type, + "attributes": { + "fullName": self.full_name, + "dateOfBirth": self.date_of_birth, + "address": self.address, + "phone": self.phone, + "email": self.email, + }, + "relationships": self.relationships + } + } + + if self.ssn: + payload["data"]["attributes"]["ssn"] = self.ssn + + if self.passport: + payload["data"]["attributes"]["passport"] = self.passport + + if self.nationality: + payload["data"]["attributes"]["nationality"] = self.nationality + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + + return payload + + def __repr__(self): + 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, CreateBusinessVirtualCreditCard, CreateBusinessCreditCard] + +class PatchIndividualDebitCard(UnitRequest): + def __init__(self,card_id: str, shipping_address: Optional[Address] = None, design: Optional[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: + payload = { + "data": { + "type": "individualDebitCard", + "attributes": {}, + } + } + + 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 + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class PatchBusinessCard(object): + 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, 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, _type: str = "businessDebitCard") -> Dict: + payload = { + "data": { + "type": _type, + "attributes": {}, + } + } + + if self.shipping_address: + payload["data"]["attributes"]["shippingAddress"] = self.shipping_address + + if self.address: + payload["data"]["attributes"]["address"] = self.address + + if self.phone: + payload["data"]["attributes"]["phone"] = self.phone + + if self.email: + payload["data"]["attributes"]["email"] = self.email + + if self.design: + payload["data"]["attributes"]["design"] = self.design + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + + return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) + + +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: + payload = { + "data": { + "type": "individualVirtualDebitCard", + "attributes": {}, + } + } + + 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 PatchBusinessVirtualCard(object): + def __init__(self, card_id: str, address: Optional[Address] = None, phone: Optional[Phone] = 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, _type: str = "businessVirtualDebitCard") -> Dict: + payload = { + "data": { + "type": _type, + "attributes": {}, + } + } + + if self.address: + payload["data"]["attributes"]["address"] = self.address + + if self.phone: + payload["data"]["attributes"]["phone"] = self.phone + + if self.email: + payload["data"]["attributes"]["email"] = self.email + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + if self.limits: + payload["data"]["attributes"]["limits"] = self.limits + + return payload + + +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, PatchBusinessCreditCard, PatchBusinessVirtualCreditCard] + +class ReplaceCardRequest(object): + def __init__(self, shipping_address: Optional[Address] = None): + self.shipping_address = shipping_address + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "replaceCard", + "attributes": {}, + } + } + + if self.shipping_address: + payload["data"]["attributes"]["shippingAddress"] = self.shipping_address + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + +PinStatus = Literal["Set", "NotSet"] + +class PinStatusDTO(object): + def __init__(self, status: PinStatus): + self.type = "pinStatus" + self.attributes = {"status": status} + + @staticmethod + def from_json_api(attributes): + return PinStatusDTO(attributes["status"]) + + +class CardLimitsDTO(object): + def __init__(self, limits: CardLevelLimits, daily_totals: CardTotals, monthly_totals: CardTotals): + self.type = "limits" + self.attributes = {"limits": limits, "dailyTotals": daily_totals, "monthlyTotals": monthly_totals} + + @staticmethod + def from_json_api(attributes): + limits = CardLevelLimits.from_json_api(attributes.get("limits")) if attributes.get("limits") else None + return CardLimitsDTO(limits, CardTotals.from_json_api(attributes.get("dailyTotals")), + CardTotals.from_json_api(attributes.get("monthlyTotals"))) + + +class ListCardParams(UnitParams): + def __init__(self, offset: int = 0, limit: int = 100, account_id: 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} + if self.customer_id: + parameters["filter[customerId]"] = self.customer_id + if self.account_id: + parameters["filter[accountId]"] = self.account_id + if 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/build/lib/unit/models/codecs.py b/build/lib/unit/models/codecs.py new file mode 100644 index 00000000..ed7d296e --- /dev/null +++ b/build/lib/unit/models/codecs.py @@ -0,0 +1,403 @@ +import json +from unit.models import * +from datetime import datetime, date + +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 +from unit.models.account import DepositAccountDTO, AccountLimitsDTO +from unit.models.customer import IndividualCustomerDTO, BusinessCustomerDTO +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, BillPaymentDTO, \ + SimulateIncomingAchPaymentDTO +from unit.models.customerToken import CustomerTokenDTO, CustomerVerificationTokenDTO +from unit.models.fee import FeeDTO +from unit.models.event import * +from unit.models.counterparty import CounterpartyDTO, CounterpartyBalanceDTO +from unit.models.webhook import WebhookDTO +from unit.models.institution import InstitutionDTO +from unit.models.statement import StatementDTO +from unit.models.atm_location import AtmLocationDTO +from unit.models.bill_pay import BillerDTO +from unit.models.api_token import APITokenDTO +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: + IndividualApplicationDTO.from_json_api(_id, _type, attributes, relationships), + + "businessApplication": lambda _id, _type, attributes, relationships: + BusinessApplicationDTO.from_json_api(_id, _type, attributes, relationships), + + "document": lambda _id, _type, attributes, relationships: + ApplicationDocumentDTO.from_json_api(_id, _type, attributes), + + "individualCustomer": lambda _id, _type, attributes, relationships: + IndividualCustomerDTO.from_json_api(_id, _type, attributes, relationships), + + "businessCustomer": lambda _id, _type, attributes, relationships: + BusinessCustomerDTO.from_json_api(_id, _type, attributes, relationships), + + "depositAccount": lambda _id, _type, attributes, relationships: + DepositAccountDTO.from_json_api(_id, _type, attributes, relationships), + + "limits": lambda _id, _type, attributes, relationships: + decode_limits(attributes), + + "individualDebitCard": lambda _id, _type, attributes, relationships: + IndividualDebitCardDTO.from_json_api(_id, _type, attributes, relationships), + + "businessDebitCard": lambda _id, _type, attributes, relationships: + BusinessDebitCardDTO.from_json_api(_id, _type, attributes, relationships), + + "individualVirtualDebitCard": lambda _id, _type, attributes, relationships: + IndividualVirtualDebitCardDTO.from_json_api(_id, _type, attributes, relationships), + + "businessVirtualDebitCard": lambda _id, _type, attributes, relationships: + BusinessVirtualDebitCardDTO.from_json_api(_id, _type, attributes, relationships), + + "originatedAchTransaction": lambda _id, _type, attributes, relationships: + OriginatedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "receivedAchTransaction": lambda _id, _type, attributes, relationships: + ReceivedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "returnedAchTransaction": lambda _id, _type, attributes, relationships: + ReturnedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "returnedReceivedAchTransaction": lambda _id, _type, attributes, relationships: + ReturnedReceivedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "dishonoredAchTransaction": lambda _id, _type, attributes, relationships: + DishonoredAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "bookTransaction": lambda _id, _type, attributes, relationships: + BookTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "purchaseTransaction": lambda _id, _type, attributes, relationships: + PurchaseTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "atmTransaction": lambda _id, _type, attributes, relationships: + AtmTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "feeTransaction": lambda _id, _type, attributes, relationships: + FeeTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "cardTransaction": lambda _id, _type, attributes, relationships: + CardTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "wireTransaction": lambda _id, _type, attributes, relationships: + WireTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "releaseTransaction": lambda _id, _type, attributes, relationships: + ReleaseTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "adjustmentTransaction": lambda _id, _type, attributes, relationships: + AdjustmentTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "interestTransaction": lambda _id, _type, attributes, relationships: + InterestTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "disputeTransaction": lambda _id, _type, attributes, relationships: + DisputeTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "checkDepositTransaction": lambda _id, _type, attributes, relationships: + CheckDepositTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "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), + + "repaidPaymentAdvanceTransaction": lambda _id, _type, attributes, relationships: + RepaidPaymentAdvanceTransactionDTO.from_json_api(_id, _type, attributes, relationships), + + "achPayment": lambda _id, _type, attributes, relationships: + AchPaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "bookPayment": lambda _id, _type, attributes, relationships: + BookPaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "wirePayment": lambda _id, _type, attributes, relationships: + WirePaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "billPayment": lambda _id, _type, attributes, relationships: + BillPaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "achReceivedPayment": lambda _id, _type, attributes, relationships: + AchReceivedPaymentDTO.from_json_api(_id, _type, attributes, relationships), + + "accountStatementDTO": lambda _id, _type, attributes, relationships: + StatementDTO.from_json_api(_id, _type, attributes, relationships), + + "sandboxAccountStatement": lambda _id, _type, attributes, relationships: + StatementDTO.from_json_api(_id, _type, attributes, relationships), + + "customerBearerToken": lambda _id, _type, attributes, relationships: + CustomerTokenDTO.from_json_api(_id, _type, attributes, relationships), + + "customerTokenVerification": lambda _id, _type, attributes, relationships: + CustomerVerificationTokenDTO.from_json_api(_id, _type, attributes, relationships), + + "achCounterparty": lambda _id, _type, attributes, relationships: + CounterpartyDTO.from_json_api(_id, _type, attributes, relationships), + + "applicationForm": lambda _id, _type, attributes, relationships: + ApplicationFormDTO.from_json_api(_id, _type, attributes, relationships), + + "fee": lambda _id, _type, attributes, relationships: + FeeDTO.from_json_api(_id, _type, attributes, relationships), + + "account.closed": lambda _id, _type, attributes, relationships: + AccountClosedEvent.from_json_api(_id, _type, attributes, relationships), + + "account.frozen": lambda _id, _type, attributes, relationships: + AccountFrozenEvent.from_json_api(_id, _type, attributes, relationships), + + "application.awaitingDocuments": lambda _id, _type, attributes, relationships: + ApplicationAwaitingDocumentsEvent.from_json_api(_id, _type, attributes, relationships), + + "application.denied": lambda _id, _type, attributes, relationships: + ApplicationDeniedEvent.from_json_api(_id, _type, attributes, relationships), + + "application.pendingReview": lambda _id, _type, attributes, relationships: + ApplicationPendingReviewEvent.from_json_api(_id, _type, attributes, relationships), + + "card.activated": lambda _id, _type, attributes, relationships: + CardActivatedEvent.from_json_api(_id, _type, attributes, relationships), + + "card.statusChanged": lambda _id, _type, attributes, relationships: + CardStatusChangedEvent.from_json_api(_id, _type, attributes, relationships), + + "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), + + "authorizationRequest.pending": lambda _id, _type, attributes, relationships: + AuthorizationRequestPendingEvent.from_json_api(_id, _type, attributes, relationships), + + "authorizationRequest.approved": lambda _id, _type, attributes, relationships: + AuthorizationRequestApprovedEvent.from_json_api(_id, _type, attributes, relationships), + + "document.rejected": lambda _id, _type, attributes, relationships: + DocumentRejectedEvent.from_json_api(_id, _type, attributes, relationships), + + "document.approved": lambda _id, _type, attributes, relationships: + DocumentApprovedEvent.from_json_api(_id, _type, attributes, relationships), + + "checkDeposit.created": lambda _id, _type, attributes, relationships: + CheckDepositCreatedEvent.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), + + "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), + + "transaction.created": lambda _id, _type, attributes, relationships: + TransactionCreatedEvent.from_json_api(_id, _type, attributes, relationships), + + "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), + + "webhook": lambda _id, _type, attributes, relationships: + WebhookDTO.from_json_api(_id, _type, attributes, relationships), + + "institution": lambda _id, _type, attributes, relationships: + InstitutionDTO.from_json_api(_id, _type, attributes, relationships), + + "atmLocation": lambda _id, _type, attributes, relationships: + AtmLocationDTO.from_json_api(_type, attributes), + + "biller": lambda _id, _type, attributes, relationships: + BillerDTO.from_json_api(_id, _type, attributes, relationships), + + "apiToken": lambda _id, _type, attributes, relationships: + APITokenDTO.from_json_api(_id, _type, attributes, relationships), + + "authorization": lambda _id, _type, attributes, relationships: + AuthorizationDTO.from_json_api(_id, _type, attributes, relationships), + + "purchaseAuthorizationRequest": lambda _id, _type, attributes, relationships: + PurchaseAuthorizationRequestDTO.from_json_api(_id, _type, attributes, relationships), + + "accountEndOfDay": lambda _id, _type, attributes, relationships: + AccountEndOfDayDTO.from_json_api(_id, _type, attributes, relationships), + + "counterpartyBalance": lambda _id, _type, attributes, relationships: + CounterpartyBalanceDTO.from_json_api(_id, _type, attributes, relationships), + + "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), + +} + + +def split_json_api_single_response(payload: Dict): + _id, _type, attributes = payload.get("id"), payload["type"], payload["attributes"] + relationships = None + + if payload.get("relationships"): + relationships = dict() + for k, v in payload.get("relationships").items(): + if isinstance(v["data"], list): + # todo: alex handle cases when relationships are in a form of array (e.g. jointAccount or documents) + continue + else: + relationships[k] = Relationship(v["data"]["type"], v["data"]["id"]) + + return _id, _type, attributes, relationships + + +def split_json_api_array_response(payload): + if not isinstance(payload, list): + raise Exception("split_json_api_array_response - couldn't parse response.") + + dtos = [] + for single_obj in payload: + dtos.append(split_json_api_single_response(single_obj)) + + return dtos + + +def decode_limits(attributes: Dict): + if "ach" in attributes.keys(): + return AccountLimitsDTO.from_json_api(attributes) + else: + return CardLimitsDTO.from_json_api(attributes) + +def mapping_wraper(_id, _type, attributes, relationships): + if _type in mappings: + return mappings[_type](_id, _type, attributes, relationships) + else: + return RawUnitObject(_id, _type, attributes, relationships) + +class DtoDecoder(object): + @staticmethod + def decode(payload): + if payload is None: + return None + # if response contains a list of dtos + if isinstance(payload, list): + dtos = split_json_api_array_response(payload) + response = [] + for _id, _type, attributes, relationships in dtos: + response.append(mapping_wraper(_id, _type, attributes, relationships)) + + return response + else: + _id, _type, attributes, relationships = split_json_api_single_response(payload) + return mapping_wraper(_id, _type, attributes, relationships) + +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): + return {"countryCode": obj.country_code, "number": obj.number} + if isinstance(obj, Address): + addr = { + "street": obj.street, + "city": obj.city, + "state": obj.state, + "postalCode": obj.postal_code, + "country": obj.country + } + + if obj.street2 is not None: + addr["street2"] = obj.street2 + return addr + if isinstance(obj, BusinessContact): + return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} + if isinstance(obj, AuthorizedUser): + return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} + 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} + if obj.status is not None: + officer["status"] = obj.status + if obj.title is not None: + officer["title"] = obj.title + if obj.ssn is not None: + officer["ssn"] = obj.ssn + if obj.passport is not None: + officer["passport"] = obj.passport + if obj.nationality is not None: + officer["nationality"] = obj.nationality + return officer + if isinstance(obj, BeneficialOwner): + beneficial_owner = {"fullName": obj.full_name, "dateOfBirth": date_utils.to_date_str(obj.date_of_birth), + "address": obj.address, "phone": obj.phone, "email": obj.email} + if obj.status is not None: + beneficial_owner["status"] = obj.status + if obj.ssn is not None: + beneficial_owner["ssn"] = obj.ssn + if obj.passport is not None: + beneficial_owner["passport"] = obj.passport + if obj.nationality is not None: + beneficial_owner["nationality"] = obj.nationality + if obj.percentage is not None: + beneficial_owner["percentage"] = obj.percentage + return beneficial_owner + if isinstance(obj, RelationshipArray): + return {"data": list(map(lambda r: r.to_dict(), obj.relationships))} + if isinstance(obj, Relationship): + return {"data": obj.to_dict()} + if isinstance(obj, Counterparty): + return {"routingNumber": obj.routing_number, "accountNumber": obj.account_number, + "accountType": obj.account_type, "name": obj.name} + if isinstance(obj, Coordinates): + return {"longitude": obj.longitude, "latitude": obj.latitude} + return json.JSONEncoder.default(self, obj) diff --git a/build/lib/unit/models/counterparty.py b/build/lib/unit/models/counterparty.py new file mode 100644 index 00000000..e043b414 --- /dev/null +++ b/build/lib/unit/models/counterparty.py @@ -0,0 +1,172 @@ +import json +from datetime import datetime, date +from typing import Optional +from unit.utils import date_utils +from unit.models import * + + +class CounterpartyDTO(object): + def __init__(self, id: str, created_at: datetime, name: str, routing_number: str, bank: Optional[str], + account_number: str, account_type: str, type: str, permissions: str, + relationships: [Dict[str, Relationship]]): + self.id = id + self.type = "achCounterparty" + self.attributes = {"createdAt": created_at, "name": name, "routingNumber": routing_number, "bank": bank, + "accountNumber": account_number, "accountType": account_type, "type": type, + "permissions": permissions} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CounterpartyDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["name"], + attributes["routingNumber"], attributes.get("bank"), attributes["accountNumber"], + attributes["accountType"], attributes["type"], attributes["permissions"], relationships) + + +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): + self.name = name + self.routing_number = routing_number + self.account_number = account_number + self.account_type = account_type + self.type = type + self.relationships = relationships + self.tags = tags + self.idempotency_key = idempotency_key + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "achCounterparty", + "attributes": { + "name": self.name, + "routingNumber": self.routing_number, + "accountNumber": self.account_number, + "accountType": self.account_type, + "type": self.type + }, + "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): + json.dumps(self.to_json_api()) + + +class CreateCounterpartyWithTokenRequest(UnitRequest): + def __init__(self, name: str, type: str, plaid_processor_token: str, relationships: [Dict[str, Relationship]], + verify_name: Optional[bool] = None, permissions: Optional[str] = None, tags: Optional[object] = None, + idempotency_key: Optional[str] = None): + self.name = name + self.type = type + self.plaid_processor_token = plaid_processor_token + self.verify_name = verify_name + self.permissions = permissions + self.relationships = relationships + self.tags = tags + self.idempotency_key = idempotency_key + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "achCounterparty", + "attributes": { + "name": self.name, + "type": self.type, + "plaidProcessorToken": self.plaid_processor_token + }, + "relationships": self.relationships + } + } + + if self.verify_name: + payload["data"]["attributes"]["verifyName"] = self.verify_name + + if self.permissions: + payload["data"]["attributes"]["permissions"] = self.permissions + + 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): + json.dumps(self.to_json_api()) + + +class PatchCounterpartyRequest(object): + def __init__(self, counterparty_id: str, plaid_processor_token: str, verify_name: Optional[bool] = None, + permissions: Optional[str] = None, tags: Optional[object] = None): + self.counterparty_id = counterparty_id + self.plaid_processor_token = plaid_processor_token + self.verify_name = verify_name + self.permissions = permissions + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "counterparty", + "attributes": { + "plaidProcessorToken": self.plaid_processor_token + } + } + } + + if self.verify_name: + payload["data"]["attributes"]["verifyName"] = self.verify_name + + if self.permissions: + payload["data"]["attributes"]["permissions"] = self.permissions + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class CounterpartyBalanceDTO(object): + def __init__(self, id: str, balance: int, available: int, relationships: [Dict[str, Relationship]]): + self.id = id + self.type = "counterpartyBalance" + self.attributes = {"balance": balance, "available": available} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CounterpartyBalanceDTO(_id, attributes["balance"], attributes["available"], relationships) + + +class ListCounterpartyParams(UnitParams): + def __init__(self, offset: int = 0, limit: int = 100, customer_id: Optional[str] = None, + tags: Optional[object] = None): + self.offset = offset + self.limit = limit + self.customer_id = customer_id + self.tags = tags + + 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 + return parameters + diff --git a/build/lib/unit/models/customer.py b/build/lib/unit/models/customer.py new file mode 100644 index 00000000..83ebb29b --- /dev/null +++ b/build/lib/unit/models/customer.py @@ -0,0 +1,158 @@ +from unit.utils import date_utils +from unit.models import * + + +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], + authorized_users: [AuthorizedUser], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + 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, "authorizedUsers": authorized_users, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return IndividualCustomerDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), + 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"), + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("tags"), relationships + ) + + +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]]): + 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} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BusinessCustomerDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["name"], + 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(attributes["authorizedUsers"]), + attributes.get("dba"), attributes.get("tags"), relationships) + +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, + 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: + payload = { + "data": { + "type": "individualCustomer", + "attributes": {} + } + } + + if self.address: + payload["data"]["attributes"]["address"] = self.address + + if self.phone: + payload["data"]["attributes"]["phone"] = self.phone + + if self.email: + payload["data"]["attributes"]["email"] = self.email + + 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 + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class PatchBusinessCustomerRequest(UnitRequest): + def __init__(self, customer_id: str, address: Optional[Address] = None, phone: Optional[Phone] = None, + contact: Optional[BusinessContact] = 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.contact = contact + self.authorized_users = authorized_users + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "businessCustomer", + "attributes": {} + } + } + + if self.address: + payload["data"]["attributes"]["address"] = self.address + + if self.phone: + payload["data"]["attributes"]["phone"] = self.phone + + if self.contact: + payload["data"]["attributes"]["contact"] = self.contact + + if self.authorized_users: + payload["data"]["attributes"]["authorizedUsers"] = self.authorized_users + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class ListCustomerParams(UnitParams): + def __init__(self, offset: int = 0, limit: int = 100, query: Optional[str] = None, email: Optional[str] = None, + tags: Optional[object] = None, sort: Optional[Literal["createdAt", "-createdAt"]] = None): + self.offset = offset + self.limit = limit + self.query = query + self.email = email + self.tags = tags + self.sort = sort + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + if self.query: + parameters["filter[query]"] = self.query + if self.email: + parameters["filter[email]"] = self.email + if self.tags: + parameters["filter[tags]"] = self.tags + if self.sort: + parameters["sort"] = self.sort + return parameters + diff --git a/build/lib/unit/models/customerToken.py b/build/lib/unit/models/customerToken.py new file mode 100644 index 00000000..856ab64a --- /dev/null +++ b/build/lib/unit/models/customerToken.py @@ -0,0 +1,89 @@ +from unit.models import * + +class CustomerTokenDTO(object): + def __init__(self, token: str, expires_in: int): + self.type = "customerBearerToken" + self.attributes = {"token": token, "expiresIn": expires_in} + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CustomerTokenDTO(attributes["token"], attributes["expiresIn"]) + +class CustomerVerificationTokenDTO(object): + def __init__(self, verification_token: str): + self.type = "customerTokenVerification" + self.attributes = {"verificationToken": verification_token} + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CustomerVerificationTokenDTO(attributes["verificationToken"]) + + +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): + self.customer_id = customer_id + self.scope = scope + self.verification_token = verification_token + self.verification_code = verification_code + self.expires_in = expires_in + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "customerToken", + "attributes": { + "scope": self.scope + } + } + } + + if self.expires_in: + payload["data"]["attributes"]["expiresIn"] = self.expires_in + + if self.verification_token: + payload["data"]["attributes"]["verificationToken"] = self.verification_token + + if self.verification_code: + payload["data"]["attributes"]["verificationCode"] = self.verification_code + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class CreateCustomerTokenVerification(UnitRequest): + + def __init__(self, customer_id: str, channel: str, phone: Optional[Phone] = None, app_hash: Optional[str] = None, + language: Optional[str] = None): + self.customer_id = customer_id + self.channel = channel + self.phone = phone + self.app_hash = app_hash + self.language = language + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "customerTokenVerification", + "attributes": { + "channel": self.channel + } + } + } + + if self.phone: + payload["data"]["attributes"]["phone"] = self.phone + + if self.app_hash: + payload["data"]["attributes"]["appHash"] = self.app_hash + + if self.language: + payload["data"]["attributes"]["language"] = self.language + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + diff --git a/build/lib/unit/models/event.py b/build/lib/unit/models/event.py new file mode 100644 index 00000000..e6fcfa72 --- /dev/null +++ b/build/lib/unit/models/event.py @@ -0,0 +1,463 @@ +import json +from datetime import datetime, date +from typing import Literal, Optional +from unit.utils import date_utils +from unit.models import * + +class BaseEvent(object): + def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.attributes = {"createdAt": created_at, "tags": tags} + self.relationships = relationships + + +class AccountClosedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, close_reason: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'account.closed' + self.attributes["closeReason"] = close_reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AccountClosedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["closeReason"], + attributes.get("tags"), relationships) + + +class AccountFrozenEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, freeze_reason: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'account.frozen' + self.attributes["freezeReason"] = freeze_reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AccountFrozenEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["freezeReason"], + attributes.get("tags"), relationships) + + +class ApplicationDeniedEvent(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 = 'application.denied' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ApplicationDeniedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), + relationships) + + +class ApplicationPendingReviewEvent(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 = 'application.pendingReview' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ApplicationPendingReviewEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes.get("tags"), relationships) + + +class ApplicationAwaitingDocumentsEvent(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 = 'application.awaitingDocuments' + + @staticmethod + 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, 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["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, + partial_approval_allowed: str, merchant: Dict[str, str], recurring: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'authorizationRequest.approved' + self.attributes["amount"] = amount + self.attributes["status"] = status + self.attributes["approvedAmount"] = approved_amount + self.attributes["partialApprovalAllowed"] = partial_approval_allowed + self.attributes["merchant"] = merchant + self.attributes["recurring"] = recurring + + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AuthorizationRequestApprovedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["amount"], attributes["status"], + attributes["approvedAmount"], attributes["partialApprovalAllowed"], + attributes["merchant"], attributes["recurring"], + attributes.get("tags"), 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, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'authorizationRequest.declined' + self.attributes["amount"] = amount + self.attributes["status"] = status + self.attributes["declineReason"] = decline_reason + self.attributes["partialApprovalAllowed"] = partial_approval_allowed + self.attributes["merchant"] = merchant + self.attributes["recurring"] = recurring + + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AuthorizationRequestDeclinedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["amount"], attributes["status"], + attributes["declineReason"], attributes["partialApprovalAllowed"], + attributes["merchant"], attributes["recurring"], + attributes.get("tags"), relationships) + + +class AuthorizationRequestPendingEvent(BaseEvent): + 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 + + + @staticmethod + def from_json_api(_id, _type, attributes, 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]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'card.activated' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardActivatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), + relationships) + + +class CardStatusChangedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, new_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 = 'card.statusChanged' + self.attributes["newStatus"] = new_status + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardStatusChangedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["newStatus"], + attributes["previousStatus"], 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]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkDeposit.created' + self.attributes["status"] = status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], 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]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkDeposit.clearing' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes.get("tags"), relationships) + + +class CheckDepositSentEvent(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.sent' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositSentEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], 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]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'checkDeposit.returned' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], 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]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'customer.created' + + @staticmethod + 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]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'document.approved' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DocumentApprovedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes.get("tags"), relationships) + +class DocumentRejectedEvent(BaseEvent): + def __init__(self, id: str, created_at: datetime, reason: str, reason_code: str, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'document.rejected' + self.attributes["reason"] = reason + self.attributes["reasonCode"] = reason_code + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DocumentRejectedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + 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]], + relationships: Optional[Dict[str, Relationship]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'payment.clearing' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + attributes.get("tags"), relationships) + +class PaymentSentEvent(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 = 'payment.sent' + self.attributes["previousStatus"] = previous_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentSentEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], + attributes.get("tags"), relationships) + +class PaymentReturnedEvent(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 = 'payment.returned' + self.attributes["previousStatus"] = previous_status + + @staticmethod + 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]]): + BaseEvent.__init__(self, id, created_at, tags, relationships) + self.type = 'statements.created' + self.attributes["period"] = period + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return StatementsCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["period"], + attributes.get("tags"), relationships) + +class TransactionCreatedEvent(BaseEvent): + 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' + self.attributes["summary"] = summary + self.attributes["direction"] = direction + self.attributes["amount"] = amount + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return TransactionCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["summary"], + attributes["direction"], attributes["amount"], attributes.get("tags"), + relationships) + +class AccountReopenedEvent(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 = 'account.reopened' + + @staticmethod + 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, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, + AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, + AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, + CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, + CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, + PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject] + + +class ListEventParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, type: Optional[List[str]] = None): + self.limit = limit + self.offset = offset + self.type = type + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + if self.type: + parameters["filter[type][]"] = self.type + return parameters diff --git a/build/lib/unit/models/fee.py b/build/lib/unit/models/fee.py new file mode 100644 index 00000000..296ce187 --- /dev/null +++ b/build/lib/unit/models/fee.py @@ -0,0 +1,50 @@ +import json +from typing import Optional +from unit.models import * + + +class FeeDTO(object): + def __init__(self, id: str, amount: int, description: str, tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "fee" + self.attributes = {"amount": amount, "description": description, "tags": tags} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return FeeDTO(_id, attributes["amount"], attributes["description"], attributes.get("tags"), relationships) + + +class CreateFeeRequest(object): + def __init__(self, amount: int, description: str, relationships: Optional[Dict[str, Relationship]], + tags: Optional[Dict[str, str]] = None, idempotency_key: Optional[str] = None): + self.amount = amount + self.description = description + self.tags = tags + self.idempotency_key = idempotency_key + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "fee", + "attributes": { + "amount": self.amount, + "description": self.description + }, + "relationships": self.relationships + } + } + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.tags + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + diff --git a/build/lib/unit/models/institution.py b/build/lib/unit/models/institution.py new file mode 100644 index 00000000..25599e68 --- /dev/null +++ b/build/lib/unit/models/institution.py @@ -0,0 +1,17 @@ +import json +from typing import Optional +from unit.models import * + + +class InstitutionDTO(object): + def __init__(self, routing_number: str, name: str, is_ach_supported: bool, is_wire_supported: bool, + address: Optional[Address] = None): + self.type = "institution" + self.attributes = {"routingNumber": routing_number, "name": name, "address": address, + "isACHSupported": is_ach_supported, "isWireSupported": is_wire_supported} + + def from_json_api(_id, _type, attributes, relationships): + return InstitutionDTO( + attributes["routingNumber"], attributes["name"], attributes["isACHSupported"], + attributes["isWireSupported"], attributes.get("address")) + diff --git a/build/lib/unit/models/payment.py b/build/lib/unit/models/payment.py new file mode 100644 index 00000000..502fb3f0 --- /dev/null +++ b/build/lib/unit/models/payment.py @@ -0,0 +1,450 @@ +from unit.utils import date_utils +from unit.models import * + +PaymentTypes = Literal["AchPayment", "BookPayment", "WirePayment", "BillPayment"] +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]]): + self.id = id + self.attributes = {"createdAt": created_at, "status": status, "direction": direction, + "description": description, "amount": amount, "reason": reason, "tags": tags} + self.relationships = relationships + +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], + settlement_date: Optional[datetime], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BasePayment.__init__(self, id, created_at, status, direction, description, amount, reason, tags, relationships) + self.type = 'achPayment' + self.attributes["counterparty"] = counterparty + self.attributes["addenda"] = addenda + self.settlement_date = settlement_date + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + settlement_date = date_utils.to_date(attributes.get("settlementDate")) if attributes.get("settlementDate") else None + return AchPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["counterparty"], attributes["direction"], attributes["description"], + 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]], + relationships: Optional[Dict[str, Relationship]]): + BasePayment.__init__(self, id, created_at, status, direction, description, amount, reason, tags, relationships) + self.type = 'bookPayment' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BookPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes.get("direction"), attributes["description"], attributes["amount"], + attributes.get("reason"), attributes.get("tags"), relationships) + +class WirePaymentDTO(BasePayment): + def __init__(self, id: str, created_at: datetime, status: PaymentStatus, counterparty: WireCounterparty, + direction: str, description: str, amount: int, reason: Optional[str], tags: Optional[Dict[str, str]], + relationships: Optional[Dict[str, Relationship]]): + BasePayment.__init__(self, id, created_at, direction, description, amount, reason, tags, relationships) + self.type = "wirePayment" + self.attributes["counterparty"] = counterparty + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return WirePaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + WireCounterparty.from_json_api(attributes["counterparty"]), attributes["direction"], + attributes["description"], attributes["amount"], 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, tags, relationships) + self.type = 'billPayment' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BillPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["direction"], attributes["description"], attributes["amount"], + attributes.get("reason"), attributes.get("tags"), relationships) + +PaymentDTO = Union[AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, BillPaymentDTO] + +AchReceivedPaymentStatus = Literal["Pending", "Advanced", "Completed", "Returned"] + +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]]): + 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} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AchReceivedPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + attributes["wasAdvanced"], attributes["completionDate"], + 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) + +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"): + self.type = type + self.amount = amount + self.description = description + self.direction = direction + self.idempotency_key = idempotency_key + self.tags = tags + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self.type, + "attributes": { + "amount": self.amount, + "direction": self.direction, + "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 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) + self.counterparty = counterparty + self.addenda = addenda + + def to_json_api(self) -> Dict: + payload = CreatePaymentBaseRequest.to_json_api(self) + + payload["data"]["attributes"]["counterparty"] = self.counterparty + + if self.addenda: + payload["data"]["attributes"]["addenda"] = self.addenda + + return payload + +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) + self.addenda = addenda + self.verify_counterparty_balance = verify_counterparty_balance + + def to_json_api(self) -> Dict: + payload = CreatePaymentBaseRequest.to_json_api(self) + + if self.addenda: + payload["data"]["attributes"]["addenda"] = self.addenda + + if self.verify_counterparty_balance: + payload["data"]["attributes"]["verifyCounterpartyBalance"] = self.verify_counterparty_balance + + 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], + idempotency_key: Optional[str], tags: Optional[Dict[str, str]], direction: str = "Credit"): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags) + self.plaid_Processor_token = plaid_Processor_token + self.counterparty_name = counterparty_name + self.verify_counterparty_balance = verify_counterparty_balance + + def to_json_api(self) -> Dict: + payload = CreatePaymentBaseRequest.to_json_api(self) + payload["data"]["attributes"]["counterparty"] = self.counterparty + payload["data"]["attributes"]["plaidProcessorToken"] = self.plaid_processor_token + + if counterparty_name: + payload["data"]["attributes"]["counterpartyName"] = self.counterparty_name + + if verify_counterparty_balance: + payload["data"]["attributes"]["verifyCounterpartyBalance"] = self.verify_counterparty_balance + + return payload + +class CreateBookPaymentRequest(CreatePaymentBaseRequest): + def __init__(self, amount: int, description: str, relationships: Dict[str, Relationship], + idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, + direction: str = "Credit"): + super().__init__(amount, description, relationships, idempotency_key, tags, direction, "bookPayment") + +class CreateWirePaymentRequest(CreatePaymentBaseRequest): + def __init__(self, amount: int, description: str, counterparty: WireCounterparty, + relationships: Dict[str, Relationship], idempotency_key: Optional[str], tags: Optional[Dict[str, str]], + direction: str = "Credit"): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, + "wirePayment") + self.counterparty = counterparty + + def to_json_api(self) -> Dict: + payload = CreatePaymentBaseRequest.to_json_api(self) + payload["data"]["attributes"]["counterparty"] = self.counterparty + return payload + +CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, + CreateBookPaymentRequest, CreateWirePaymentRequest] + +class PatchAchPaymentRequest(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": "achPayment", + "attributes": { + "tags": self.tags + } + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + +class PatchBookPaymentRequest(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": "bookPayment", + "attributes": { + "tags": self.tags + } + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + +PatchPaymentRequest = Union[PatchAchPaymentRequest, PatchBookPaymentRequest] + +class ListPaymentParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, + customer_id: Optional[str] = None, tags: Optional[object] = None, + status: Optional[List[PaymentStatus]] = None, type: Optional[List[PaymentTypes]] = None, + direction: Optional[List[PaymentDirections]] = None, since: Optional[str] = None, + until: Optional[str] = None, sort: Optional[Literal["createdAt", "-createdAt"]] = None, + include: Optional[str] = None): + self.limit = limit + self.offset = offset + self.account_id = account_id + self.customer_id = customer_id + self.tags = tags + self.status = status + self.type = type + self.direction = direction + 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.customer_id: + parameters["filter[customerId]"] = self.customer_id + if self.account_id: + parameters["filter[accountId]"] = self.account_id + if self.tags: + parameters["filter[tags]"] = self.tags + 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 + if self.direction: + for idx, direction_filter in enumerate(self.direction): + parameters[f"filter[direction][{idx}]"] = direction_filter + if self.since: + parameters["filter[since]"] = self.since + if self.until: + parameters["filter[until]"] = self.until + if self.sort: + parameters["sort"] = self.sort + if self.include: + parameters["include"] = self.include + return parameters + +class ListReceivedPaymentParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, + customer_id: Optional[str] = None, tags: Optional[object] = None, + status: Optional[List[AchReceivedPaymentStatus]] = None, + direction: Optional[List[PaymentDirections]] = None, include_completed: Optional[bool] = None, + sort: Optional[Literal["createdAt", "-createdAt"]] = None, include: Optional[str] = None): + self.limit = limit + self.offset = offset + self.account_id = account_id + self.customer_id = customer_id + self.tags = tags + self.status = status + self.include_completed = include_completed + self.sort = sort + self.include = include + + 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.account_id: + parameters["filter[accountId]"] = self.account_id + if self.tags: + parameters["filter[tags]"] = self.tags + if self.include_completed: + parameters["filter[includeCompleted]"] = self.include_completed + if self.status: + for idx, status_filter in enumerate(self.status): + parameters[f"filter[status][{idx}]"] = status_filter + if self.sort: + parameters["sort"] = self.sort + if self.include: + parameters["include"] = self.include + return parameters diff --git a/build/lib/unit/models/repayment.py b/build/lib/unit/models/repayment.py new file mode 100644 index 00000000..3554b94b --- /dev/null +++ b/build/lib/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/build/lib/unit/models/returnAch.py b/build/lib/unit/models/returnAch.py new file mode 100644 index 00000000..6c972246 --- /dev/null +++ b/build/lib/unit/models/returnAch.py @@ -0,0 +1,29 @@ +import json +from typing import Literal +from unit.models import * + +AchReturnReason = Literal["InsufficientFunds", "Unauthorized", "UncollectedFunds"] + + +class ReturnReceivedAchTransactionRequest(UnitRequest): + def __init__(self, transaction_id: str, reason: AchReturnReason, relationships: [Dict[str, Relationship]]): + self.transaction_id = transaction_id + self.reason = reason + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "returnAch", + "attributes": { + "reason": self.reason + }, + "relationships": self.relationships + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + diff --git a/build/lib/unit/models/reward.py b/build/lib/unit/models/reward.py new file mode 100644 index 00000000..07f1535b --- /dev/null +++ b/build/lib/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/build/lib/unit/models/statement.py b/build/lib/unit/models/statement.py new file mode 100644 index 00000000..590e2604 --- /dev/null +++ b/build/lib/unit/models/statement.py @@ -0,0 +1,46 @@ +import json +from unit.models import * + + +class StatementDTO(object): + def __init__(self, id: str, _type: str, period: str, relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = _type + self.attributes = {"period": period} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return StatementDTO(_id, _type, attributes["period"], relationships) + + +OutputType = Literal["html", "pdf"] + +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 + self.output_type = output_type + self.language = language + self.customer_id = customer_id + + +class ListStatementParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, customer_id: Optional[str] = None, + account_id: Optional[str] = None, sort: Optional[Literal["period", "-period"]] = None): + self.limit = limit + self.offset = offset + self.customer_id = customer_id + self.account_id = account_id + self.sort = sort + + 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.account_id: + parameters["filter[accountId]"] = self.account_id + if self.sort: + parameters["sort"] = self.sort + return parameters + diff --git a/build/lib/unit/models/transaction.py b/build/lib/unit/models/transaction.py new file mode 100644 index 00000000..58aec0fa --- /dev/null +++ b/build/lib/unit/models/transaction.py @@ -0,0 +1,466 @@ +from unit.utils import date_utils +from unit.models import * + + +class BaseTransactionDTO(object): + 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]]): + self.id = id + self.attributes = {"createdAt": created_at, "direction": direction, "amount": amount, "balance": balance, + "summary": summary, "tags": tags} + self.relationships = relationships + + +class OriginatedAchTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, + summary: str, description: str, addenda: Optional[str], counterparty: Counterparty, + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'originatedAchTransaction' + self.attributes["description"] = description + self.attributes["addenda"] = addenda + self.attributes["counterparty"] = counterparty + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return OriginatedAchTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], attributes["description"], + attributes.get("addenda"), Counterparty.from_json_api(attributes["counterparty"]), + attributes.get("tags"), relationships) + + +class ReceivedAchTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, + summary: str, description: str, addenda: Optional[str], company_name: str, + counterparty_routing_number: str, trace_number: Optional[str], sec_code: 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 = 'receivedAchTransaction' + self.attributes["description"] = description + self.attributes["addenda"] = addenda + self.attributes["companyName"] = company_name + self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number + self.attributes["traceNumber"] = trace_number + self.attributes["secCode"] = sec_code + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReceivedAchTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], attributes["description"], + attributes.get("addenda"), attributes["companyName"], attributes["counterpartyRoutingNumber"], + attributes.get("traceNumber"), attributes.get("secCode"), attributes.get("tags"), relationships) + + +class ReturnedAchTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, + summary: str, company_name: str, counterparty_name: str, counterparty_routing_number: str, 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 = 'returnedAchTransaction' + self.attributes["companyName"] = company_name + self.attributes["counterpartyName"] = counterparty_name + self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number + self.attributes["reason"] = reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReturnedAchTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], attributes["amount"], + attributes["balance"], attributes["summary"], attributes["companyName"], attributes["counterpartyName"], + attributes["counterpartyRoutingNumber"], attributes["reason"], attributes.get("tags"), relationships) + + +class ReturnedReceivedAchTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, + company_name: str, 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 = 'returnedReceivedAchTransaction' + self.attributes["companyName"] = company_name + self.attributes["reason"] = reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReturnedReceivedAchTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], attributes["companyName"], + attributes["reason"], attributes.get("tags"), relationships) + + +class DishonoredAchTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, + company_name: str, counterparty_routing_number: str, reason: str, trace_number: Optional[str], + sec_code: 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 = 'dishonoredAchTransaction' + self.attributes["companyName"] = company_name + self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number + self.attributes["traceNumber"] = trace_number + self.attributes["reason"] = reason + self.attributes["secCode"] = sec_code + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DishonoredAchTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], attributes["companyName"], + attributes["counterpartyRoutingNumber"], attributes["reason"], attributes.get("traceNumber"), + attributes.get("secCode"), attributes.get("tags"), 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]] = 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 + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return BookTransactionDTO( + 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]]): + 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 + self.attributes["coordinates"] = coordinates + self.attributes["recurring"] = recurring + self.attributes["interchange"] = interchange + self.attributes["ecommerce"] = ecommerce + self.attributes["cardPresent"] = card_present + self.attributes["paymentMethod"] = payment_method + self.attributes["digitalWallet"] = digital_wallet + self.attributes["cardVerificationData"] = card_verification_data + self.attributes["cardNetwork"] = card_network + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + 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.get("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) + + +class AtmTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, + summary: str, card_last_4_digits: str, atm_name: str, atm_location: Optional[str], surcharge: int, + interchange: Optional[int], 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 = 'atmTransaction' + self.attributes["cardLast4Digits"] = card_last_4_digits + self.attributes["atmName"] = atm_name + self.attributes["atmLocation"] = atm_location + self.attributes["surcharge"] = surcharge + self.attributes["interchange"] = interchange + self.attributes["cardNetwork"] = card_network + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AtmTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes["cardLast4Digits"], attributes["atmName"], attributes.get("atmLocation"), + attributes["surcharge"], attributes.get("interchange"), attributes.get("cardNetwork"), + attributes.get("tags"), relationships) + + +class FeeTransactionDTO(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 = 'feeTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return FeeTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes.get("tags"), 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], + 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 + self.attributes["recurring"] = recurring + self.attributes["interchange"] = interchange + self.attributes["paymentMethod"] = payment_method + self.attributes["digitalWallet"] = digital_wallet + self.attributes["cardVerificationData"] = card_verification_data + self.attributes["cardNetwork"] = card_network + + + @staticmethod + 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.get("recurring"), attributes.get("interchange"), + attributes.get("paymentMethod"), attributes.get("digitalWallet"), + attributes.get("cardVerificationData"), attributes.get("cardNetwork"), + attributes.get("tags"), relationships) + + +class CardReversalTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, + summary: str, card_last_4_digits: 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 = 'cardReversalTransaction' + self.attributes["cardLast4Digits"] = card_last_4_digits + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CardReversalTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes["cardLast4Digits"], attributes.get("tags"), relationships) + + +class WireTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, + summary: str, counterparty: Counterparty, description: str, + originator_to_beneficiary_information: str, sender_reference: str, + reference_for_beneficiary: str, beneficiary_information: str, + beneficiary_advice_information: 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 = 'wireTransaction' + self.attributes["description"] = description + self.attributes["counterparty"] = counterparty + self.attributes["originatorToBeneficiaryInformation"] = originator_to_beneficiary_information + self.attributes["senderReference"] = sender_reference + self.attributes["referenceForBeneficiary"] = reference_for_beneficiary + self.attributes["beneficiaryInformation"] = beneficiary_information + self.attributes["beneficiaryAdviceInformation"] = beneficiary_advice_information + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return WireTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + Counterparty.from_json_api(attributes["counterparty"]), attributes["description"], + attributes.get("originatorToBeneficiaryInformation"), attributes.get("senderReference"), + attributes.get("referenceForBeneficiary"), attributes.get("beneficiaryInformation"), + attributes.get("beneficiaryAdviceInformation"), attributes.get("tags"), relationships) + + +class ReleaseTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, sender_name: str, sender_address: Address, + sender_account_number: str, counterparty: Counterparty, amount: int, direction: str, + description: 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 = 'releaseTransaction' + self.attributes["description"] = description + self.attributes["senderName"] = sender_name + self.attributes["senderAddress"] = sender_address + self.attributes["senderAccountNumber"] = sender_account_number + self.attributes["counterparty"] = counterparty + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReleaseTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["senderName"], + Address.from_json_api(attributes["senderAddress"]), + attributes["senderAccountNumber"], + Counterparty.from_json_api(attributes["counterparty"]), attributes["amount"], + attributes["direction"], attributes["description"], attributes["balance"], + attributes["summary"], attributes.get("tags"), relationships) + + +class AdjustmentTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, + description: 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 = 'adjustmentTransaction' + self.attributes["description"] = description + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AdjustmentTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], + attributes["summary"], attributes["description"], attributes.get("tags"), + relationships) + + +class InterestTransactionDTO(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 = 'interestTransaction' + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return InterestTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes.get("tags"), relationships) + + +class DisputeTransactionDTO(BaseTransactionDTO): + def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, dispute_id: str, + summary: str, 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 = 'disputeTransaction' + self.attributes["disputeId"] = dispute_id + self.attributes["reason"] = reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return DisputeTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["disputeId"], + attributes["summary"], attributes["reason"], attributes.get("tags"), relationships) + + +class CheckDepositTransactionDTO(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 = 'checkDepositTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes.get("tags"), relationships) + + +class ReturnedCheckDepositTransactionDTO(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]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'returnedCheckDepositTransaction' + self.attributes["reason"] = reason + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReturnedCheckDepositTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes["reason"], 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]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'paymentAdvanceTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentAdvanceTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["direction"], + attributes["amount"], attributes["balance"], attributes["summary"], + attributes.get("tags"), relationships) + +class RepaidPaymentAdvanceTransactionDTO(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]]): + BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) + self.type = 'repaidPaymentAdvanceTransaction' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return RepaidPaymentAdvanceTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["direction"], + attributes["amount"], 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] + + +class PatchTransactionRequest(BaseTransactionDTO, UnitRequest): + def __init__(self, account_id: str, transaction_id: str, tags: Optional[Dict[str, str]] = None): + self.account_id = account_id + self.transaction_id = transaction_id + self.tags = tags + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "transaction", + "attributes": {} + } + } + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload + + +class ListTransactionParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, + customer_id: Optional[str] = None, query: Optional[str] = None, tags: Optional[object] = None, + since: Optional[str] = None, until: Optional[str] = None, card_id: Optional[str] = None, + type: Optional[List[str]] = None, exclude_fees: Optional[bool] = None, + sort: Optional[Literal["createdAt", "-createdAt"]] = None, include: Optional[str] = None): + self.limit = limit + self.offset = offset + self.account_id = account_id + self.customer_id = customer_id + self.query = query + self.tags = tags + self.since = since + self.until = until + self.card_id = card_id + self.type = type + self.exclude_fees = exclude_fees + self.sort = sort + self.include = include + + 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.account_id: + parameters["filter[accountId]"] = self.account_id + if self.query: + parameters["filter[query]"] = self.query + if self.tags: + parameters["filter[tags]"] = self.tags + if self.since: + parameters["filter[since]"] = self.since + if self.until: + parameters["filter[until]"] = self.until + if self.card_id: + parameters["filter[cardId]"] = self.card_id + if self.type: + for idx, type_filter in enumerate(self.type): + parameters[f"filter[type][{idx}]"] = type_filter + if self.exclude_fees: + parameters["filter[excludeFees]"] = self.exclude_fees + if self.sort: + parameters["sort"] = self.sort + if self.include: + parameters["include"] = self.include + return parameters \ No newline at end of file diff --git a/build/lib/unit/models/webhook.py b/build/lib/unit/models/webhook.py new file mode 100644 index 00000000..c19d82c1 --- /dev/null +++ b/build/lib/unit/models/webhook.py @@ -0,0 +1,92 @@ +import json +from datetime import datetime, date +from unit.utils import date_utils +from unit.models import * +from typing import Literal + +ContentType = Literal["Json", "JsonAPI"] +WebhookStatus = Literal["Enabled", "Disabled"] + + +class WebhookDTO(object): + def __init__(self, id: str, created_at: datetime, label: str, url: str, status: WebhookStatus, + content_type: ContentType, token: str): + self.id = id + self.type = 'webhook' + self.attributes = {"createdAt": created_at, "label": label, "url": url, "status": status, + "contentType": content_type, "token": token} + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return WebhookDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["label"], attributes["url"], + attributes["status"], attributes["contentType"], attributes["token"]) + + +class CreateWebhookRequest(object): + def __init__(self, label: str, url: str, token: str, content_type: ContentType): + self.label = label + self.url = url + self.token = token + self.content_type = content_type + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "webhook", + "attributes": { + "label": self.label, + "url": self.url, + "token": self.token, + "contentType": self.content_type + } + } + } + + return payload + + def __repr__(self): + json.dumps(self.to_json_api()) + + +class PatchWebhookRequest(object): + def __init__(self, webhook_id: str, label: Optional[str] = None, url: Optional[str] = None, + content_type: Optional[ContentType] = None, token: Optional[str] = None): + self.webhook_id = webhook_id + self.label = label + self.url = url + self.content_type = content_type + self.token = token + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "webhook", + "attributes": {} + } + } + + if self.label: + payload["data"]["attributes"]["label"] = self.label + + if self.url: + payload["data"]["attributes"]["url"] = self.url + + if self.content_type: + payload["data"]["attributes"]["contentType"] = self.content_type + + if self.token: + payload["data"]["attributes"]["token"] = self.token + + return payload + + +class ListWebhookParams(UnitParams): + def __init__(self, limit: int = 100, offset: int = 0): + self.limit = limit + self.offset = offset + + def to_dict(self) -> Dict: + parameters = {"page[limit]": self.limit, "page[offset]": self.offset} + return parameters + diff --git a/build/lib/unit/utils/__init__.py b/build/lib/unit/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build/lib/unit/utils/date_utils.py b/build/lib/unit/utils/date_utils.py new file mode 100644 index 00000000..6dbdbec6 --- /dev/null +++ b/build/lib/unit/utils/date_utils.py @@ -0,0 +1,13 @@ +from datetime import date, datetime + + +def to_datetime(dt: str): + return datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%f%z") + + +def to_date(d: str): + return date.fromisoformat(d) + + +def to_date_str(d: date): + return d.strftime("%Y-%m-%d") diff --git a/unit/api/account_resource.py b/unit/api/account_resource.py index 06fb60bd..a034469d 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): @@ -52,7 +52,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/repayment_resource.py b/unit/api/repayment_resource.py new file mode 100644 index 00000000..d55a69d0 --- /dev/null +++ b/unit/api/repayment_resource.py @@ -0,0 +1,38 @@ +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_create(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/models/__init__.py b/unit/models/__init__.py index fb288cbe..a25d06dc 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -2,6 +2,29 @@ from typing import TypeVar, Generic, Union, Optional, Literal, List, Dict 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): diff --git a/unit/models/account.py b/unit/models/account.py index 9768faf9..70dcc444 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): @@ -220,19 +311,34 @@ def __repr__(self): 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/card.py b/unit/models/card.py index 69d5e5f4..bf0c4ab0 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, - 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, - "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, + "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"], 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, @@ -89,8 +113,41 @@ def from_json_api(_id, _type, attributes, relationships): 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")) + +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)) -Card = Union[IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO, BusinessVirtualDebitCardDTO] +Card = Union[IndividualDebitCardDTO, BusinessDebitCardDTO, IndividualVirtualDebitCardDTO, BusinessVirtualDebitCardDTO, + BusinessVirtualCreditCardDTO, BusinessCreditCardDTO] class CreateIndividualDebitCard(UnitRequest): @@ -133,32 +190,34 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessDebitCard(UnitRequest): +class CreateBusinessCard(object): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLevelLimits] = None, shipping_address: Optional[Address] = None, + 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, relationships: Optional[Dict[str, Relationship]] = 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.limits = limits self.shipping_address = shipping_address self.ssn = ssn self.passport = passport self.nationality = nationality self.design = design self.idempotency_key = idempotency_key - self.tags = tags or {} - self.relationships = relationships or {} + 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, @@ -173,9 +232,6 @@ 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.ssn: payload["data"]["attributes"]["ssn"] = self.ssn @@ -194,10 +250,29 @@ 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 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, @@ -231,29 +306,28 @@ def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessVirtualDebitCard(UnitRequest): +class CreateBusinessVirtualCard(object): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - status: CardStatus, limits: Optional[CardLevelLimits] = None, - passport: Optional[str] = None, nationality: Optional[str] = None, - idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, - relationships: Optional[Dict[str, Relationship]] = None): + 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.limits = limits self.nationality = nationality self.idempotency_key = idempotency_key - self.tags = tags or {} - self.relationships = relationships or {} + 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, @@ -265,11 +339,8 @@ def to_json_api(self) -> Dict: } } - if self.limits: - payload["data"]["attributes"]["limits"] = self.limits - - if self.limits: - payload["data"]["attributes"]["limits"] = self.limits + if self.ssn: + payload["data"]["attributes"]["ssn"] = self.ssn if self.passport: payload["data"]["attributes"]["passport"] = self.passport @@ -283,14 +354,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 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(UnitRequest): def __init__(self,card_id: str, shipping_address: Optional[Address] = None, design: Optional[str] = None, @@ -327,20 +411,23 @@ def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessDebitCard(UnitRequest): +class PatchBusinessCard(object): 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, - limits: CardLevelLimits = 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.limits = limits + 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": {}, } } @@ -348,9 +435,6 @@ 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.address: payload["data"]["attributes"]["address"] = self.address @@ -366,10 +450,22 @@ 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 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): @@ -395,27 +491,25 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessVirtualDebitCard(UnitRequest): +class PatchBusinessVirtualCard(object): def __init__(self, card_id: str, address: Optional[Address] = None, phone: Optional[Phone] = None, - email: Optional[str] = None, limits: CardLevelLimits = 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.limits = limits 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": {}, } } - if self.limits: - payload["data"]["attributes"]["limits"] = self.limits - if self.address: payload["data"]["attributes"]["address"] = self.address @@ -425,15 +519,26 @@ def to_json_api(self) -> Dict: if self.email: payload["data"]["attributes"]["email"] = self.email - payload["data"]["attributes"]["tags"] = self.tags or {} + 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): @@ -452,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"] @@ -481,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} @@ -496,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/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_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO new file mode 100644 index 00000000..c3be286a --- /dev/null +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -0,0 +1,18 @@ +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 diff --git a/unit_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt new file mode 100644 index 00000000..da93de6f --- /dev/null +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -0,0 +1,65 @@ +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/bill_pay_resource.py +unit/api/card_resource.py +unit/api/counterparty_resource.py +unit/api/customerToken_resource.py +unit/api/customer_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/benificial_owner.py +unit/models/bill_pay.py +unit/models/card.py +unit/models/codecs.py +unit/models/counterparty.py +unit/models/customer.py +unit/models/customerToken.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 From dfbe4f725940b9c648a71be312a587153f477150 Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Tue, 11 Jul 2023 13:21:55 -0400 Subject: [PATCH 076/181] Remove build/ folder and add to gitignore --- .gitignore | 2 + build/lib/unit/__init__.py | 52 -- build/lib/unit/api/__init__.py | 0 .../unit/api/account_end_of_day_resource.py | 19 - build/lib/unit/api/account_resource.py | 71 -- build/lib/unit/api/ach_resource.py | 34 - build/lib/unit/api/api_token_resource.py | 34 - .../lib/unit/api/applicationForm_resource.py | 37 -- build/lib/unit/api/application_resource.py | 90 --- build/lib/unit/api/atmLocation_resource.py | 34 - .../api/authorization_request_resource.py | 46 -- build/lib/unit/api/authorization_resource.py | 28 - build/lib/unit/api/base_resource.py | 44 -- build/lib/unit/api/bill_pay_resource.py | 22 - build/lib/unit/api/card_resource.py | 113 ---- build/lib/unit/api/counterparty_resource.py | 59 -- build/lib/unit/api/customerToken_resource.py | 29 - build/lib/unit/api/customer_resource.py | 40 -- build/lib/unit/api/event_resource.py | 33 - build/lib/unit/api/fee_resource.py | 19 - build/lib/unit/api/institution_resource.py | 17 - build/lib/unit/api/payment_resource.py | 57 -- .../lib/unit/api/received_payment_resource.py | 45 -- build/lib/unit/api/repayment_resource.py | 38 -- build/lib/unit/api/returnAch_resource.py | 20 - build/lib/unit/api/reward_resource.py | 39 -- build/lib/unit/api/statement_resource.py | 38 -- build/lib/unit/api/transaction_resource.py | 41 -- build/lib/unit/api/webhook_resource.py | 72 -- build/lib/unit/models/__init__.py | 301 --------- build/lib/unit/models/account.py | 344 ---------- build/lib/unit/models/account_end_of_day.py | 41 -- build/lib/unit/models/api_token.py | 48 -- build/lib/unit/models/application.py | 564 ---------------- build/lib/unit/models/applicationForm.py | 103 --- build/lib/unit/models/atm_location.py | 28 - build/lib/unit/models/authorization.py | 61 -- .../lib/unit/models/authorization_request.py | 98 --- build/lib/unit/models/benificial_owner.py | 26 - build/lib/unit/models/bill_pay.py | 21 - build/lib/unit/models/card.py | 617 ------------------ build/lib/unit/models/codecs.py | 403 ------------ build/lib/unit/models/counterparty.py | 172 ----- build/lib/unit/models/customer.py | 158 ----- build/lib/unit/models/customerToken.py | 89 --- build/lib/unit/models/event.py | 463 ------------- build/lib/unit/models/fee.py | 50 -- build/lib/unit/models/institution.py | 17 - build/lib/unit/models/payment.py | 450 ------------- build/lib/unit/models/repayment.py | 114 ---- build/lib/unit/models/returnAch.py | 29 - build/lib/unit/models/reward.py | 137 ---- build/lib/unit/models/statement.py | 46 -- build/lib/unit/models/transaction.py | 466 ------------- build/lib/unit/models/webhook.py | 92 --- build/lib/unit/utils/__init__.py | 0 build/lib/unit/utils/date_utils.py | 13 - 57 files changed, 2 insertions(+), 6122 deletions(-) delete mode 100644 build/lib/unit/__init__.py delete mode 100644 build/lib/unit/api/__init__.py delete mode 100644 build/lib/unit/api/account_end_of_day_resource.py delete mode 100644 build/lib/unit/api/account_resource.py delete mode 100644 build/lib/unit/api/ach_resource.py delete mode 100644 build/lib/unit/api/api_token_resource.py delete mode 100644 build/lib/unit/api/applicationForm_resource.py delete mode 100644 build/lib/unit/api/application_resource.py delete mode 100644 build/lib/unit/api/atmLocation_resource.py delete mode 100644 build/lib/unit/api/authorization_request_resource.py delete mode 100644 build/lib/unit/api/authorization_resource.py delete mode 100644 build/lib/unit/api/base_resource.py delete mode 100644 build/lib/unit/api/bill_pay_resource.py delete mode 100644 build/lib/unit/api/card_resource.py delete mode 100644 build/lib/unit/api/counterparty_resource.py delete mode 100644 build/lib/unit/api/customerToken_resource.py delete mode 100644 build/lib/unit/api/customer_resource.py delete mode 100644 build/lib/unit/api/event_resource.py delete mode 100644 build/lib/unit/api/fee_resource.py delete mode 100644 build/lib/unit/api/institution_resource.py delete mode 100644 build/lib/unit/api/payment_resource.py delete mode 100644 build/lib/unit/api/received_payment_resource.py delete mode 100644 build/lib/unit/api/repayment_resource.py delete mode 100644 build/lib/unit/api/returnAch_resource.py delete mode 100644 build/lib/unit/api/reward_resource.py delete mode 100644 build/lib/unit/api/statement_resource.py delete mode 100644 build/lib/unit/api/transaction_resource.py delete mode 100644 build/lib/unit/api/webhook_resource.py delete mode 100644 build/lib/unit/models/__init__.py delete mode 100644 build/lib/unit/models/account.py delete mode 100644 build/lib/unit/models/account_end_of_day.py delete mode 100644 build/lib/unit/models/api_token.py delete mode 100644 build/lib/unit/models/application.py delete mode 100644 build/lib/unit/models/applicationForm.py delete mode 100644 build/lib/unit/models/atm_location.py delete mode 100644 build/lib/unit/models/authorization.py delete mode 100644 build/lib/unit/models/authorization_request.py delete mode 100644 build/lib/unit/models/benificial_owner.py delete mode 100644 build/lib/unit/models/bill_pay.py delete mode 100644 build/lib/unit/models/card.py delete mode 100644 build/lib/unit/models/codecs.py delete mode 100644 build/lib/unit/models/counterparty.py delete mode 100644 build/lib/unit/models/customer.py delete mode 100644 build/lib/unit/models/customerToken.py delete mode 100644 build/lib/unit/models/event.py delete mode 100644 build/lib/unit/models/fee.py delete mode 100644 build/lib/unit/models/institution.py delete mode 100644 build/lib/unit/models/payment.py delete mode 100644 build/lib/unit/models/repayment.py delete mode 100644 build/lib/unit/models/returnAch.py delete mode 100644 build/lib/unit/models/reward.py delete mode 100644 build/lib/unit/models/statement.py delete mode 100644 build/lib/unit/models/transaction.py delete mode 100644 build/lib/unit/models/webhook.py delete mode 100644 build/lib/unit/utils/__init__.py delete mode 100644 build/lib/unit/utils/date_utils.py 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/build/lib/unit/__init__.py b/build/lib/unit/__init__.py deleted file mode 100644 index 99bda338..00000000 --- a/build/lib/unit/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -from unit.api.application_resource import ApplicationResource -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.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 -from unit.api.returnAch_resource import ReturnAchResource -from unit.api.applicationForm_resource import ApplicationFormResource -from unit.api.fee_resource import FeeResource -from unit.api.event_resource import EventResource -from unit.api.webhook_resource import WebhookResource -from unit.api.institution_resource import InstitutionResource -from unit.api.atmLocation_resource import AtmLocationResource -from unit.api.bill_pay_resource import BillPayResource -from unit.api.api_token_resource import APITokenResource -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 - -__all__ = ["api", "models", "utils"] - - -class Unit(object): - def __init__(self, api_url, token): - self.applications = ApplicationResource(api_url, token) - self.customers = CustomerResource(api_url, token) - self.accounts = AccountResource(api_url, token) - self.cards = CardResource(api_url, token) - self.transactions = TransactionResource(api_url, token) - self.payments = PaymentResource(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) - self.returnAch = ReturnAchResource(api_url, token) - self.applicationForms = ApplicationFormResource(api_url, token) - self.fees = FeeResource(api_url, token) - self.events = EventResource(api_url, token) - self.webhooks = WebhookResource(api_url, token) - self.institutions = InstitutionResource(api_url, token) - self.atmLocations = AtmLocationResource(api_url, token) - self.billPays = BillPayResource(api_url, token) - self.api_tokens = APITokenResource(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) diff --git a/build/lib/unit/api/__init__.py b/build/lib/unit/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/build/lib/unit/api/account_end_of_day_resource.py b/build/lib/unit/api/account_end_of_day_resource.py deleted file mode 100644 index 71490714..00000000 --- a/build/lib/unit/api/account_end_of_day_resource.py +++ /dev/null @@ -1,19 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.account_end_of_day import * -from unit.models.codecs import DtoDecoder - - -class AccountEndOfDayResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "account-end-of-day" - - def list(self, params: ListAccountEndOfDayParams = None) -> Union[UnitResponse[List[AccountEndOfDayDTO]], UnitError]: - params = params or ListAccountEndOfDayParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[AccountEndOfDayDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/account_resource.py b/build/lib/unit/api/account_resource.py deleted file mode 100644 index a034469d..00000000 --- a/build/lib/unit/api/account_resource.py +++ /dev/null @@ -1,71 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.account import * -from unit.models.codecs import DtoDecoder - -class AccountResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "accounts" - - 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): - data = response.json().get("data") - return UnitResponse[AccountDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def close_account(self, request: CloseAccountRequest) -> Union[UnitResponse[AccountDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.account_id}/close", 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 reopen_account(self, account_id: str, reason: str = "ByCustomer") -> Union[UnitResponse[AccountDTO], UnitError]: - response = super().post(f"{self.resource}/{account_id}/reopen", {'reason': reason}) - 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): - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[AccountDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListAccountParams = None) -> Union[UnitResponse[List[AccountDTO]], UnitError]: - params = params or ListAccountParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[AccountDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) - else: - return UnitError.from_json_api(response.json()) - - 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): - data = response.json().get("data") - return UnitResponse[AccountDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def limits(self, account_id: str) -> Union[UnitResponse[AccountLimitsDTO], UnitError]: - response = super().get(f"{self.resource}/{account_id}/limits", None) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[AccountLimitsDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/ach_resource.py b/build/lib/unit/api/ach_resource.py deleted file mode 100644 index f19835db..00000000 --- a/build/lib/unit/api/ach_resource.py +++ /dev/null @@ -1,34 +0,0 @@ -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/build/lib/unit/api/api_token_resource.py b/build/lib/unit/api/api_token_resource.py deleted file mode 100644 index e44206d5..00000000 --- a/build/lib/unit/api/api_token_resource.py +++ /dev/null @@ -1,34 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.api_token import * -from unit.models.codecs import DtoDecoder - - -class APITokenResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "users" - - def create(self, request: CreateAPITokenRequest) -> Union[UnitResponse[APITokenDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.user_id}/api-tokens", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[APITokenDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def list(self, user_id: str) -> Union[UnitResponse[List[APITokenDTO]], UnitError]: - response = super().get(f"{self.resource}/{user_id}/api-tokens") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[APITokenDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def revoke(self, user_id: str, token_id: str) -> Union[UnitResponse, UnitError]: - response = super().delete(f"{self.resource}/{user_id}/api-tokens/{token_id}") - if super().is_20x(response.status_code): - return UnitResponse([], None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/applicationForm_resource.py b/build/lib/unit/api/applicationForm_resource.py deleted file mode 100644 index 3ef83f3e..00000000 --- a/build/lib/unit/api/applicationForm_resource.py +++ /dev/null @@ -1,37 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.applicationForm import * -from unit.models.codecs import DtoDecoder - - -class ApplicationFormResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "application-forms" - - def create(self, request: CreateApplicationFormRequest) -> Union[UnitResponse[ApplicationFormDTO], 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[ApplicationFormDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def get(self, application_form_id: str, include: Optional[str] = "") -> Union[UnitResponse[ApplicationFormDTO], UnitError]: - response = super().get(f"{self.resource}/{application_form_id}", {"include": include}) - if super().is_20x(response.status_code): - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[ApplicationFormDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListApplicationFormParams = None) -> Union[UnitResponse[List[ApplicationFormDTO]], UnitError]: - params = params or ListApplicationFormParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[ApplicationFormDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/application_resource.py b/build/lib/unit/api/application_resource.py deleted file mode 100644 index 82d041e7..00000000 --- a/build/lib/unit/api/application_resource.py +++ /dev/null @@ -1,90 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.application import * -from unit.models.codecs import DtoDecoder - - -class ApplicationResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "applications" - - def create(self, request: Union[CreateIndividualApplicationRequest, CreateBusinessApplicationRequest]) -> Union[UnitResponse[ApplicationDTO], UnitError]: - payload = request.to_json_api() - response = super().post(self.resource, payload) - - if response.ok: - data = response.json().get("data") - included = response.json().get("included") - 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()) - - def list(self, params: ListApplicationParams = None) -> Union[UnitResponse[List[ApplicationDTO]], UnitError]: - params = params or ListApplicationParams() - response = super().get(self.resource, params.to_dict()) - if response.status_code == 200: - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[ApplicationDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def get(self, application_id: str) -> Union[UnitResponse[ApplicationDTO], UnitError]: - response = super().get(f"{self.resource}/{application_id}") - if response.status_code == 200: - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[ApplicationDTO](DtoDecoder.decode(data), DtoDecoder.decode(included)) - else: - return UnitError.from_json_api(response.json()) - - def upload(self, request: UploadDocumentRequest): - url = f"{self.resource}/{request.application_id}/documents/{request.document_id}" - if request.is_back_side: - url += "/back" - - headers = {} - - if request.file_type == "jpeg": - headers = {"Content-Type": "image/jpeg"} - if request.file_type == "png": - headers = {"Content-Type": "image/png"} - if request.file_type == "pdf": - headers = {"Content-Type": "application/pdf"} - - response = super().put(url, request.file, headers) - if response.status_code == 200: - data = response.json().get("data") - return UnitResponse[ApplicationDocumentDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def update(self, request: PatchApplicationRequest) -> Union[UnitResponse[ApplicationDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.application_id}", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[ApplicationDTO](DtoDecoder.decode(data), None) - 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/build/lib/unit/api/atmLocation_resource.py b/build/lib/unit/api/atmLocation_resource.py deleted file mode 100644 index 5e8b8bab..00000000 --- a/build/lib/unit/api/atmLocation_resource.py +++ /dev/null @@ -1,34 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.atm_location import * -from unit.models.codecs import DtoDecoder, UnitEncoder - -class AtmLocationResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "atm-locations" - - """ - UnitEncoder must be imported here and not in the model class to no cause circular importing. - """ - def get(self, request: GetAtmLocationParams) -> Union[UnitResponse[List[AtmLocationDTO]], UnitError]: - params = {} - - if request.coordinates: - params["filter[coordinates]"] = json.dumps(request.coordinates, cls=UnitEncoder) - - if request.address: - params["filter[address]"] = json.dumps(request.address, cls=UnitEncoder) - - if request.postal_code: - params["filter[postalCode]"] = json.dumps(request.postal_code, cls=UnitEncoder) - - if request.search_radius: - params["filter[searchRadius]"] = request.search_radius - - response = super().get(self.resource, params) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[AtmLocationDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/authorization_request_resource.py b/build/lib/unit/api/authorization_request_resource.py deleted file mode 100644 index 56815ee8..00000000 --- a/build/lib/unit/api/authorization_request_resource.py +++ /dev/null @@ -1,46 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.authorization_request import * -from unit.models.codecs import DtoDecoder - - -class AuthorizationRequestResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "authorization-requests" - - def get(self, authorization_id: str) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: - response = super().get(f"{self.resource}/{authorization_id}") - 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()) - - def list(self, params: ListPurchaseAuthorizationRequestParams = None) \ - -> Union[UnitResponse[List[PurchaseAuthorizationRequestDTO]], UnitError]: - params = params or ListPurchaseAuthorizationRequestParams() - response = super().get(self.resource, params.to_dict()) - 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()) - - def approve(self, request: ApproveAuthorizationRequest) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.authorization_id}/approve", 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()) - - def decline(self, request: DeclineAuthorizationRequest) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.authorization_id}/decline", 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/build/lib/unit/api/authorization_resource.py b/build/lib/unit/api/authorization_resource.py deleted file mode 100644 index 28a3fec7..00000000 --- a/build/lib/unit/api/authorization_resource.py +++ /dev/null @@ -1,28 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.authorization import * -from unit.models.codecs import DtoDecoder - - -class AuthorizationResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "authorizations" - - def get(self, authorization_id: str, include_non_authorized: Optional[bool] = False) -> Union[UnitResponse[AuthorizationDTO], UnitError]: - params = {"filter[includeNonAuthorized]": include_non_authorized} - - response = super().get(f"{self.resource}/{authorization_id}", params) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[AuthorizationDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListAuthorizationParams = None) -> Union[UnitResponse[List[AuthorizationDTO]], UnitError]: - params = params or ListAuthorizationParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[AuthorizationDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/base_resource.py b/build/lib/unit/api/base_resource.py deleted file mode 100644 index b8b06547..00000000 --- a/build/lib/unit/api/base_resource.py +++ /dev/null @@ -1,44 +0,0 @@ -import json -from typing import Optional, Dict -import requests -from unit.models.codecs import UnitEncoder - - -class BaseResource(object): - def __init__(self, api_url, token): - self.api_url = api_url - self.token = token - self.headers = { - "content-type": "application/vnd.api+json", - "authorization": f"Bearer {self.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 post(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = 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)) - - def patch(self, resource: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = 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)) - - 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 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 __merge_headers(self, headers: Optional[Dict[str, str]] = None): - if not headers: - return self.headers - else: - merged = self.headers.copy() - merged.update(**headers) - return merged - - def is_20x(self, status: int): - return status == 200 or status == 201 or status == 204 - diff --git a/build/lib/unit/api/bill_pay_resource.py b/build/lib/unit/api/bill_pay_resource.py deleted file mode 100644 index ee4c7342..00000000 --- a/build/lib/unit/api/bill_pay_resource.py +++ /dev/null @@ -1,22 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.bill_pay import * -from unit.models.codecs import DtoDecoder - - -class BillPayResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "payments/billpay/billers" - - def get(self, params: GetBillersParams) -> Union[UnitResponse[List[BillerDTO]], UnitError]: - parameters = {"name": params.name} - if params.page: - parameters["page"] = params.page - - response = super().get(self.resource, parameters) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[BillerDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/card_resource.py b/build/lib/unit/api/card_resource.py deleted file mode 100644 index e51dea54..00000000 --- a/build/lib/unit/api/card_resource.py +++ /dev/null @@ -1,113 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.card import * -from unit.models.codecs import DtoDecoder - - -class CardResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "cards" - - def create(self, request: CreateCardRequest) -> Union[UnitResponse[Card], 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[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def report_stolen(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: - response = super().post(f"{self.resource}/{card_id}/report-stolen") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def report_lost(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: - response = super().post(f"{self.resource}/{card_id}/report-lost") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def close(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: - response = super().post(f"{self.resource}/{card_id}/close") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def freeze(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: - response = super().post(f"{self.resource}/{card_id}/freeze") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def unfreeze(self, card_id: str) -> Union[UnitResponse[Card], UnitError]: - response = super().post(f"{self.resource}/{card_id}/unfreeze") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def replace(self, card_id: str, shipping_address: Optional[Address]) -> Union[UnitResponse[Union[IndividualDebitCardDTO, BusinessDebitCardDTO]], UnitError]: - request = ReplaceCardRequest(shipping_address) - payload = request.to_json_api() - response = super().post(f"{self.resource}/{card_id}/replace", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Union[IndividualDebitCardDTO, BusinessDebitCardDTO]](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def update(self, request: PatchCardRequest) -> Union[UnitResponse[Card], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.card_id}", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[Card](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def get(self, card_id: str, include: Optional[str] = "") -> Union[UnitResponse[Card], UnitError]: - response = super().get(f"{self.resource}/{card_id}", {"include": include}) - if super().is_20x(response.status_code): - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[Card](DtoDecoder.decode(data), DtoDecoder.decode(included)) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListCardParams = None) -> Union[UnitResponse[List[Card]], UnitError]: - params = params or ListCardParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[Card](DtoDecoder.decode(data), DtoDecoder.decode(included)) - else: - return UnitError.from_json_api(response.json()) - - def get_pin_status(self, card_id: str) -> Union[UnitResponse[PinStatusDTO], UnitError]: - response = super().get(f"{self.resource}/{card_id}/secure-data/pin/status") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[PinStatusDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - - def limits(self, card_id: str) -> Union[UnitResponse[CardLimitsDTO], UnitError]: - response = super().get(f"{self.resource}/{card_id}/limits") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CardLimitsDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/counterparty_resource.py b/build/lib/unit/api/counterparty_resource.py deleted file mode 100644 index 3cf1518e..00000000 --- a/build/lib/unit/api/counterparty_resource.py +++ /dev/null @@ -1,59 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.counterparty import * -from unit.models.codecs import DtoDecoder - - -class CounterpartyResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "counterparties" - - def create(self, request: Union[CreateCounterpartyRequest, CreateCounterpartyWithTokenRequest]) -> Union[UnitResponse[CounterpartyDTO], 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[CounterpartyDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def update(self, request: PatchCounterpartyRequest) -> Union[UnitResponse[CounterpartyDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.counterparty_id}", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CounterpartyDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def delete(self, counterparty_id: str) -> Union[UnitResponse, UnitError]: - response = super().delete(f"{self.resource}/{counterparty_id}") - if super().is_20x(response.status_code): - return UnitResponse([], None) - else: - return UnitError.from_json_api(response.json()) - - def get(self, counterparty_id: str) -> Union[UnitResponse[CounterpartyDTO], UnitError]: - response = super().get(f"{self.resource}/{counterparty_id}") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CounterpartyDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListCounterpartyParams = None) -> Union[UnitResponse[List[CounterpartyDTO]], UnitError]: - params = params or ListCounterpartyParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CounterpartyDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def get_balance(self, counterparty_id: str) -> Union[UnitResponse[CounterpartyBalanceDTO], UnitError]: - response = super().get(f"{self.resource}/{counterparty_id}/balance") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CounterpartyBalanceDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/customerToken_resource.py b/build/lib/unit/api/customerToken_resource.py deleted file mode 100644 index 04e2dfd7..00000000 --- a/build/lib/unit/api/customerToken_resource.py +++ /dev/null @@ -1,29 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.customerToken import * -from unit.models.codecs import DtoDecoder - - -class CustomerTokenResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "customers" - - def create_token(self, request: CreateCustomerToken) -> Union[UnitResponse[CustomerTokenDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.customer_id}/token", payload) - - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CustomerTokenDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def create_token_verification(self, request: CreateCustomerTokenVerification) -> Union[UnitResponse[CustomerVerificationTokenDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.customer_id}/token/verification", payload) - - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[CustomerVerificationTokenDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/customer_resource.py b/build/lib/unit/api/customer_resource.py deleted file mode 100644 index d164452c..00000000 --- a/build/lib/unit/api/customer_resource.py +++ /dev/null @@ -1,40 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.customer import * -from unit.models.codecs import DtoDecoder - - -class CustomerResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "customers" - - def update(self, request: Union[PatchIndividualCustomerRequest, PatchBusinessCustomerRequest]) -> Union[UnitResponse[CustomerDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.customer_id}", payload) - - if response.ok: - data = response.json().get("data") - if data["type"] == "individualCustomer": - return UnitResponse[IndividualCustomerDTO](DtoDecoder.decode(data), None) - else: - return UnitResponse[BusinessCustomerDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - - def get(self, customer_id: str) -> Union[UnitResponse[CustomerDTO], UnitError]: - response = super().get(f"{self.resource}/{customer_id}") - if response.status_code == 200: - data = response.json().get("data") - return UnitResponse[CustomerDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListCustomerParams = None) -> Union[UnitResponse[List[CustomerDTO]], UnitError]: - params = params or ListCustomerParams() - response = super().get(self.resource, params.to_dict()) - if response.status_code == 200: - data = response.json().get("data") - return UnitResponse[CustomerDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/event_resource.py b/build/lib/unit/api/event_resource.py deleted file mode 100644 index aaec2f97..00000000 --- a/build/lib/unit/api/event_resource.py +++ /dev/null @@ -1,33 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.event import * -from unit.models.codecs import DtoDecoder - - -class EventResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "events" - - def get(self, event_id: str) -> Union[UnitResponse[EventDTO], UnitError]: - response = super().get(f"{self.resource}/{event_id}") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[EventDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListEventParams = None) -> Union[UnitResponse[List[EventDTO]], UnitError]: - params = params or ListEventParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[EventDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def fire(self, event_id: str) -> Union[UnitResponse, UnitError]: - response = super().post(f"{self.resource}/{event_id}") - if super().is_20x(response.status_code): - return UnitResponse([], None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/fee_resource.py b/build/lib/unit/api/fee_resource.py deleted file mode 100644 index 68bb2756..00000000 --- a/build/lib/unit/api/fee_resource.py +++ /dev/null @@ -1,19 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.fee import * -from unit.models.codecs import DtoDecoder - - -class FeeResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "fees" - - def create(self, request: CreateFeeRequest) -> Union[UnitResponse[FeeDTO], 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[FeeDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/institution_resource.py b/build/lib/unit/api/institution_resource.py deleted file mode 100644 index d91ce96d..00000000 --- a/build/lib/unit/api/institution_resource.py +++ /dev/null @@ -1,17 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.institution import * -from unit.models.codecs import DtoDecoder - - -class InstitutionResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "institutions" - - def get(self, routing_number: str) -> Union[UnitResponse[InstitutionDTO], UnitError]: - response = super().get(f"{self.resource}/{routing_number}", None) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[InstitutionDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) diff --git a/build/lib/unit/api/payment_resource.py b/build/lib/unit/api/payment_resource.py deleted file mode 100644 index 33c13fda..00000000 --- a/build/lib/unit/api/payment_resource.py +++ /dev/null @@ -1,57 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.payment import * -from unit.models.codecs import DtoDecoder, split_json_api_single_response - - -class PaymentResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "payments" - - def create(self, request: CreatePaymentRequest) -> Union[UnitResponse[PaymentDTO], 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[PaymentDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def update(self, request: PatchPaymentRequest) -> Union[UnitResponse[PaymentDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.payment_id}", payload) - 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()) - - def get(self, payment_id: str, include: Optional[str] = "") -> Union[UnitResponse[PaymentDTO], UnitError]: - response = super().get(f"{self.resource}/{payment_id}", {"include": include}) - if response.status_code == 200: - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[PaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListPaymentParams = None) -> Union[UnitResponse[List[PaymentDTO]], UnitError]: - params = params or ListPaymentParams() - response = super().get(self.resource, params.to_dict()) - if response.status_code == 200: - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[PaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) - 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()) diff --git a/build/lib/unit/api/received_payment_resource.py b/build/lib/unit/api/received_payment_resource.py deleted file mode 100644 index b453d6ac..00000000 --- a/build/lib/unit/api/received_payment_resource.py +++ /dev/null @@ -1,45 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.payment import * -from unit.models.codecs import DtoDecoder - - -class ReceivedPaymentResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "received-payments" - - def update(self, request: PatchPaymentRequest) -> Union[UnitResponse[AchReceivedPaymentDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.payment_id}", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def get(self, payment_id: str, include: Optional[str] = "") -> Union[UnitResponse[AchReceivedPaymentDTO], UnitError]: - response = super().get(f"{self.resource}/{payment_id}", {"include": include}) - if response.status_code == 200: - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListReceivedPaymentParams = None) -> Union[UnitResponse[List[AchReceivedPaymentDTO]], UnitError]: - params = params or ListReceivedPaymentParams() - response = super().get(self.resource, params.to_dict()) - if response.status_code == 200: - data = response.json().get("data") - included = response.json().get("included") - return UnitResponse[AchReceivedPaymentDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) - else: - return UnitError.from_json_api(response.json()) - - 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()) \ No newline at end of file diff --git a/build/lib/unit/api/repayment_resource.py b/build/lib/unit/api/repayment_resource.py deleted file mode 100644 index d55a69d0..00000000 --- a/build/lib/unit/api/repayment_resource.py +++ /dev/null @@ -1,38 +0,0 @@ -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_create(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/build/lib/unit/api/returnAch_resource.py b/build/lib/unit/api/returnAch_resource.py deleted file mode 100644 index 948479de..00000000 --- a/build/lib/unit/api/returnAch_resource.py +++ /dev/null @@ -1,20 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.transaction import ReturnedReceivedAchTransactionDTO -from unit.models.returnAch import * -from unit.models.codecs import DtoDecoder - - -class ReturnAchResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "returns" - - def return_ach(self, request: ReturnReceivedAchTransactionRequest) -> Union[UnitResponse[ReturnedReceivedAchTransactionDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/{request.transaction_id}", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[ReturnedReceivedAchTransactionDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/reward_resource.py b/build/lib/unit/api/reward_resource.py deleted file mode 100644 index 8cafa463..00000000 --- a/build/lib/unit/api/reward_resource.py +++ /dev/null @@ -1,39 +0,0 @@ -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/build/lib/unit/api/statement_resource.py b/build/lib/unit/api/statement_resource.py deleted file mode 100644 index be711e1f..00000000 --- a/build/lib/unit/api/statement_resource.py +++ /dev/null @@ -1,38 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.statement import * -from unit.models.codecs import DtoDecoder - - -class StatementResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "statements" - - def get(self, params: GetStatementParams) -> Union[UnitResponse[str], UnitError]: - parameters = {"language": params.language} - if params.customer_id: - parameters["filter[customerId]"] = params.customer_id - - response = super().get(f"{self.resource}/{params.statement_id}/{params.output_type}", parameters) - if response.status_code == 200: - return UnitResponse[bytes](response.content, None) - else: - return UnitError.from_json_api(response.json()) - - def get_bank_verification(self, account_id: str, include_proof_of_funds: Optional[bool] = False) -> Union[UnitResponse[str], UnitError]: - 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) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListStatementParams = None) -> Union[UnitResponse[List[StatementDTO]], UnitError]: - params = params or ListStatementParams() - response = super().get(self.resource, params.to_dict()) - if response.status_code == 200: - data = response.json().get("data") - return UnitResponse[StatementDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - diff --git a/build/lib/unit/api/transaction_resource.py b/build/lib/unit/api/transaction_resource.py deleted file mode 100644 index abcb8e38..00000000 --- a/build/lib/unit/api/transaction_resource.py +++ /dev/null @@ -1,41 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.transaction import * -from unit.models.codecs import DtoDecoder -from unit.models.transaction import * - - -class TransactionResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "transactions" - - 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") - 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[TransactionDTO]], UnitError]: - params = params or ListTransactionParams() - response = super().get(self.resource, params.to_dict()) - 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 update(self, request: PatchTransactionRequest) -> Union[UnitResponse[TransactionDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"accounts/{request.account_id}/{self.resource}/{request.transaction_id}", payload) - 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()) - diff --git a/build/lib/unit/api/webhook_resource.py b/build/lib/unit/api/webhook_resource.py deleted file mode 100644 index 78349feb..00000000 --- a/build/lib/unit/api/webhook_resource.py +++ /dev/null @@ -1,72 +0,0 @@ -from unit.api.base_resource import BaseResource -from unit.models.webhook import * -from unit.models.codecs import DtoDecoder -import hmac -from hashlib import sha1 -import base64 - - -class WebhookResource(BaseResource): - def __init__(self, api_url, token): - super().__init__(api_url, token) - self.resource = "webhooks" - - def create(self, request: CreateWebhookRequest) -> Union[UnitResponse[WebhookDTO], 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[WebhookDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def get(self, webhook_id: str) -> Union[UnitResponse[WebhookDTO], UnitError]: - response = super().get(f"{self.resource}/{webhook_id}") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def list(self, params: ListWebhookParams = None) -> Union[UnitResponse[List[WebhookDTO]], UnitError]: - params = params or ListWebhookParams() - response = super().get(self.resource, params.to_dict()) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def update(self, request: PatchWebhookRequest) -> Union[UnitResponse[WebhookDTO], UnitError]: - payload = request.to_json_api() - response = super().patch(f"{self.resource}/{request.webhook_id}", payload) - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def enable(self, webhook_id: str) -> Union[UnitResponse[WebhookDTO], UnitError]: - response = super().post(f"{self.resource}/{webhook_id}/enable") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def disable(self, webhook_id: str) -> Union[UnitResponse[WebhookDTO], UnitError]: - response = super().post(f"{self.resource}/{webhook_id}/disable") - if super().is_20x(response.status_code): - data = response.json().get("data") - return UnitResponse[WebhookDTO](DtoDecoder.decode(data), None) - else: - return UnitError.from_json_api(response.json()) - - def verify(self, signature: str, secret: str, payload): - mac = hmac.new( - secret.encode(), - msg=json.dumps(payload, separators=(',', ':'), ensure_ascii=False).encode('utf-8'), - digestmod=sha1, - ) - res = base64.encodebytes(mac.digest()).decode().rstrip('\n') - return res == signature diff --git a/build/lib/unit/models/__init__.py b/build/lib/unit/models/__init__.py deleted file mode 100644 index a25d06dc..00000000 --- a/build/lib/unit/models/__init__.py +++ /dev/null @@ -1,301 +0,0 @@ -import json -from typing import TypeVar, Generic, Union, Optional, Literal, List, Dict -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 - self.id = _id - - def to_dict(self): - return {"type": self.type, "id": self.id} - - -T = TypeVar('T') - -class RelationshipArray(Generic[T]): - def __init__(self, l: List[T]): - self.relationships = l - - -class UnitResponse(Generic[T]): - def __init__(self, data: Union[T, List[T]], included): - self.data = data - self.included = included - - @staticmethod - def from_json_api(data: str): - pass - - -class UnitRequest(object): - def to_json_api(self) -> Dict: - pass - -class UnitParams(object): - def to_dict(self) -> Dict: - pass - -class RawUnitObject(object): - def __init__(self, _id, _type, attributes, relationships): - self.id = _id - self.type = _type - 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): - self.title = title - self.status = status - self.detail = detail - self.details = details - self.source = source - - def __str__(self): - return self.detail - - -class UnitError(object): - def __init__(self, errors: List[UnitErrorPayload]): - self.errors = errors - - @staticmethod - 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)) - ) - - 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]}) - - -Status = Literal["Approved", "Denied", "PendingReview"] -Title = Literal["CEO", "COO", "CFO", "President"] -EntityType = Literal["Corporation", "LLC", "Partnership"] - -class FullName(object): - 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): - self.street = street - self.street2 = street2 - self.city = city - self.state = state - self.postal_code = postal_code - self.country = 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)) - - -class Phone(object): - def __init__(self, country_code: str, number: str): - self.country_code = country_code - self.number = number - - @staticmethod - def from_json_api(data: Dict): - return Phone(data.get("countryCode"), data.get("number")) - - -class BusinessContact(object): - def __init__(self, full_name: FullName, email: str, phone: Phone): - self.full_name = full_name - self.email = email - self.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"))) - - -class Officer(object): - 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): - 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.title = title - self.ssn = ssn - self.passport = passport - self.nationality = nationality - - @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): - 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(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"))) - return beneficial_owners - - -class AuthorizedUser(object): - def __init__(self, full_name: FullName, email: str, phone: Phone): - self.full_name = full_name - self.email = email - self.phone = phone - - @staticmethod - 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"))) - return authorized_users - -class WireCounterparty(object): - 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 - self.address = 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): - self.routing_number = routing_number - self.account_number = account_number - self.account_type = account_type - self.name = name - - @staticmethod - def from_json_api(data: Dict): - return Counterparty(data["routingNumber"], data["accountNumber"], data["accountType"], data["name"]) - -class Coordinates(object): - def __init__(self, longitude: int, latitude: int): - self.longitude = longitude - self.latitude = latitude - - @staticmethod - def from_json_api(data: Dict): - if data: - return Coordinates(data["longitude"], data["latitude"]) - else: - return None - - -class Merchant(object): - def __init__(self, name: str, type: int, category: Optional[str], location: Optional[str]): - self.name = name - self.type = type - self.category = category - self.location = location - - @staticmethod - def from_json_api(data: Dict): - return Merchant(data["name"], data["type"], data.get("category"), data.get("location")) - -class CardLevelLimits(object): - 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 - self.monthly_purchase = monthly_purchase - - @staticmethod - def from_json_api(data: Dict): - return CardLevelLimits(data["dailyWithdrawal"], data["dailyPurchase"], data["monthlyWithdrawal"], - data["monthlyPurchase"]) - -class CardTotals(object): - def __init__(self, withdrawals: int, deposits: int, purchases: int): - self.withdrawals = withdrawals - self.deposits = deposits - self.purchases = purchases - - @staticmethod - def from_json_api(data: Dict): - return CardTotals(data["withdrawals"], data["deposits"], data["purchases"]) - - -class DeviceFingerprint(object): - def __init__(self, value: str, provider: str = "iovation"): - self.value = value - self.provider = provider - - def to_json_api(self): - return { - "value": self.value, - "provider": self.provider, - } - - @classmethod - def from_json_api(cls, data: Dict): - return cls(value=data["value"], provider=data["provider"]) diff --git a/build/lib/unit/models/account.py b/build/lib/unit/models/account.py deleted file mode 100644 index 70dcc444..00000000 --- a/build/lib/unit/models/account.py +++ /dev/null @@ -1,344 +0,0 @@ -from unit.utils import date_utils - -from unit.models import * - -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, - account_number: str, currency: str, balance: int, hold: int, available: int, status: AccountStatus, - tags: Optional[Dict[str, str]], close_reason: Optional[CloseReason], - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "depositAccount" - self.attributes = {"name": name, "createdAt": created_at, "depositProduct": deposit_product, - "routingNumber": routing_number, "accountNumber": account_number, "currency": currency, - "balance": balance, "hold": hold, "available": available, "status": status, - "closeReason": close_reason, "tags": tags} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return DepositAccountDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["name"], attributes["depositProduct"], - attributes["routingNumber"], attributes["accountNumber"], attributes["currency"], attributes["balance"], - attributes["hold"], attributes["available"], attributes["status"], attributes.get("tags"), - attributes.get("closeReason"), relationships - ) - - -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): - def __init__(self, deposit_product: str, relationships: Optional[Dict[str, Union[Relationship, RelationshipArray]]], - tags: Optional[Dict[str, str]] = None, idempotency_key: Optional[str] = None): - self.deposit_product = deposit_product - self.tags = tags - self.idempotency_key = idempotency_key - self.relationships = relationships - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "depositAccount", - "attributes": { - "depositProduct": self.deposit_product, - }, - "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): - 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): - self.account_id = account_id - self.deposit_product = deposit_product - self.tags = tags - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "depositAccount", - "attributes": {} - } - } - - if self.deposit_product: - payload["data"]["attributes"]["depositProduct"] = self.deposit_product - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - 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): - self.debits = debits - self.credits = credits - - @staticmethod - def from_json_api(data: Dict): - return AchTotals(data["debits"], data["credits"]) - - -class AchLimits(object): - def __init__(self, daily_debit: int, daily_credit: int, monthly_debit: int, monthly_credit: int, - daily_debit_soft: int, monthly_debit_soft: int): - self.daily_debit = daily_debit - self.daily_credit = daily_credit - self.monthly_debit = monthly_debit - self.monthly_credit = monthly_credit - self.daily_debit_soft = daily_debit_soft - self.monthly_debit_soft = monthly_debit_soft - - @staticmethod - def from_json_api(data: Dict): - return AchLimits(data["dailyDebit"], data["dailyCredit"], data["monthlyDebit"], data["monthlyCredit"], - data["dailyDebitSoft"], data["monthlyDebitSoft"]) - - -class AccountAchLimits(object): - def __init__(self, limits: AchLimits, totals_daily: AchTotals, totals_monthly: AchTotals): - self.limits = limits - self.totals_daily = totals_daily - self.totals_monthly = totals_monthly - - @staticmethod - def from_json_api(data: Dict): - return AccountAchLimits(AchLimits.from_json_api(data["limits"]), AchTotals.from_json_api(data["totalsDaily"]), - AchTotals.from_json_api(data["totalsMonthly"])) - - -class CardLimits(object): - def __init__(self, daily_withdrawal: int, daily_deposit: int, daily_purchase: int, daily_card_transaction: int): - self.daily_withdrawal = daily_withdrawal - self.daily_deposit = daily_deposit - self.daily_purchase = daily_purchase - self.daily_card_transaction = daily_card_transaction - - @staticmethod - def from_json_api(data: Dict): - return CardLimits(data["dailyWithdrawal"], data["dailyDeposit"], - data["dailyPurchase"], data["dailyCardTransaction"]) - -class CardTotals(object): - def __init__(self, withdrawals: int, deposits: int, purchases: int, card_transactions: int): - self.withdrawals = withdrawals - self.deposits = deposits - self.purchases = purchases - self.card_transactions = card_transactions - - @staticmethod - def from_json_api(data: Dict): - return CardTotals(data["withdrawals"], data["deposits"], data["purchases"], data["cardTransactions"]) - -class AccountCardLimits(object): - def __init__(self, limits: CardLimits, totals_daily: CardTotals): - self.limits = limits - self.totals_daily = totals_daily - - @staticmethod - def from_json_api(data: Dict): - return AccountCardLimits(CardLimits.from_json_api(data["limits"]), - CardTotals.from_json_api(data["totalsDaily"])) - - -class CheckDepositLimits(object): - def __init__(self, daily: int, monthly: int, daily_soft: int, monthly_soft: int): - self.daily = daily - self.monthly = monthly - self.daily_soft = daily_soft - self.monthly_soft = monthly_soft - - @staticmethod - def from_json_api(data: Dict): - return CheckDepositLimits(data["daily"], data["monthly"], data["dailySoft"], data["monthlySoft"]) - - -class CheckDepositAccountLimits(object): - def __init__(self, limits: CheckDepositLimits, totals_daily: int, totals_monthly: int): - self.limits = limits - self.totals_daily = totals_daily - self.totals_monthly = totals_monthly - - @staticmethod - def from_json_api(data: Dict): - return CheckDepositAccountLimits(CheckDepositLimits.from_json_api(data["limits"]), data["totalsDaily"], - data["totalsMonthly"]) - - -class AccountLimitsDTO(object): - def __init__(self, ach: AccountAchLimits, card: AccountCardLimits, check_deposit: CheckDepositAccountLimits): - self.type = "limits" - self.attributes = {"ach": ach, "card": card, "checkDeposit": check_deposit} - - @staticmethod - def from_json_api(attributes): - return AccountLimitsDTO(AccountAchLimits.from_json_api(attributes["ach"]), - AccountCardLimits.from_json_api(attributes["card"]), - CheckDepositAccountLimits.from_json_api(attributes["checkDeposit"])) - - -class CloseAccountRequest(UnitRequest): - def __init__(self, account_id: str, reason: Optional[Literal["ByCustomer", "Fraud"]] = "ByCustomer"): - self.account_id = account_id - self.reason = reason - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "accountClose", - "attributes": { - "reason": self.reason, - } - } - } - - return payload - - def __repr__(self): - 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[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]"] = 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/build/lib/unit/models/account_end_of_day.py b/build/lib/unit/models/account_end_of_day.py deleted file mode 100644 index b22bf22a..00000000 --- a/build/lib/unit/models/account_end_of_day.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -from typing import Optional -from unit.models import * - - -class AccountEndOfDayDTO(object): - def __init__(self, id: str, date: str, balance: int, hold: int, available: int, - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "accountEndOfDay" - self.attributes = {"date": date, "balance": balance, "hold": hold, "available": available} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AccountEndOfDayDTO(_id, attributes["date"], attributes["balance"], attributes["hold"], - attributes["available"], relationships) - - -class ListAccountEndOfDayParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, - customer_id: Optional[str] = None, since: Optional[str] = None, until: Optional[str] = None): - self.limit = limit - self.offset = offset - self.account_id = account_id - self.customer_id = customer_id - self.since = since - self.until = until - - 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.account_id: - parameters["filter[accountId]"] = self.account_id - if self.since: - parameters["filter[since]"] = self.since - if self.until: - parameters["filter[until]"] = self.until - return parameters - diff --git a/build/lib/unit/models/api_token.py b/build/lib/unit/models/api_token.py deleted file mode 100644 index a08e3550..00000000 --- a/build/lib/unit/models/api_token.py +++ /dev/null @@ -1,48 +0,0 @@ -from unit.models import * -from unit.utils import date_utils - - -class APITokenDTO(object): - def __init__(self, id: str, created_at: datetime, description: str, expiration: datetime, token: Optional[str], - source_ip: Optional[str]): - self.id = id - self.type = "apiToken" - self.attributes = {"createdAt": created_at, "description": description, "expiration": expiration, - "token": token, "sourceIp": source_ip} - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return APITokenDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["description"], - date_utils.to_datetime(attributes["expiration"]), attributes.get("token"), - attributes.get("sourceIp")) - - -class CreateAPITokenRequest(object): - def __init__(self, user_id: str, description: str, scope: str, expiration: datetime, - source_ip: Optional[str] = None): - self.user_id = user_id - self.description = description - self.scope = scope - self.expiration = expiration - self.source_ip = source_ip - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "apiToken", - "attributes": { - "description": self.description, - "scope": self.scope, - "expiration": self.expiration - } - } - } - - if self.source_ip: - payload["data"]["attributes"]["sourceIp"] = self.source_ip - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - diff --git a/build/lib/unit/models/application.py b/build/lib/unit/models/application.py deleted file mode 100644 index bfb450b7..00000000 --- a/build/lib/unit/models/application.py +++ /dev/null @@ -1,564 +0,0 @@ -from unit.utils import date_utils -from unit.models import * -from typing import IO - -ApplicationStatus = Literal["Approved", "Denied", "Pending", "PendingReview"] - -DocumentType = Literal[ - "IdDocument", - "Passport", - "AddressVerification", - "CertificateOfIncorporation", - "EmployerIdentificationNumberConfirmation", -] - -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" -] - - -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]], - ): - 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.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"]), - 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, - ) - - -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]], - ): - 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.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, - ) - - -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, - ): - self.full_name = full_name - self.date_of_birth = date_of_birth - self.address = address - self.email = email - self.phone = phone - self.ip = ip - self.ein = ein - self.dba = dba - self.sole_proprietorship = sole_proprietorship - self.ssn = ssn - self.passport = passport - self.nationality = nationality - self.device_fingerprints = device_fingerprints - self.idempotency_key = idempotency_key - self.tags = tags - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "individualApplication", - "attributes": { - "fullName": self.full_name, - "dateOfBirth": date_utils.to_date_str(self.date_of_birth), - "address": self.address, - "email": self.email, - "phone": self.phone, - }, - } - } - - if self.ip: - payload["data"]["attributes"]["ip"] = self.ip - - if self.ein: - payload["data"]["attributes"]["ein"] = self.ein - - if self.dba: - payload["data"]["attributes"]["dba"] = self.dba - - if self.sole_proprietorship: - payload["data"]["attributes"][ - "soleProprietorship" - ] = self.sole_proprietorship - - if self.ssn: - payload["data"]["attributes"]["ssn"] = self.ssn - - if self.passport: - payload["data"]["attributes"]["passport"] = self.passport - - if self.nationality: - payload["data"]["attributes"]["nationality"] = self.nationality - - if self.idempotency_key: - 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 - ] - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -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, - 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 - ): - self.name = name - self.address = address - self.phone = phone - self.state_of_incorporation = state_of_incorporation - self.ein = ein - self.contact = contact - self.officer = officer - self.beneficial_owners = beneficial_owners - self.entity_type = entity_type - 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 - - def to_json_api(self) -> dict: - payload = { - "data": { - "type": "businessApplication", - "attributes": { - "name": self.name, - "address": self.address, - "phone": self.phone, - "stateOfIncorporation": self.state_of_incorporation, - "ein": self.ein, - "contact": self.contact, - "officer": self.officer, - "beneficialOwners": self.beneficial_owners, - "entityType": self.entity_type, - "industry": self.industry, - "annualRevenue": self.annual_revenue, - "numberOfEmployees": self.number_of_employees, - "cashFlow": self.cash_flow, - "yearOfIncorporation": self.year_of_incorporation, - "countriesOfOperation": self.countries_of_operation, - "stockSymbol": self.stock_symbol, - "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 - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -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], - ): - 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, - } - - @staticmethod - def from_json_api(_id, _type, attributes): - 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"), - ) - - -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, - ): - self.application_id = application_id - self.document_id = document_id - self.file = file - self.file_type = file_type - self.is_back_side = is_back_side - - -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, - ): - self.offset = offset - self.limit = limit - self.email = email - self.query = query - self.sort = sort - self.tags = tags - - def to_dict(self) -> Dict: - parameters = {"page[limit]": self.limit, "page[offset]": self.offset} - if self.email: - parameters["filter[email]"] = self.email - if self.query: - parameters["filter[query]"] = self.query - if self.tags: - parameters["filter[tags]"] = self.tags - if self.sort: - parameters["sort"] = self.sort - return parameters - - -class PatchApplicationRequest(UnitRequest): - 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": {}}} - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - 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/build/lib/unit/models/applicationForm.py b/build/lib/unit/models/applicationForm.py deleted file mode 100644 index 99708e7a..00000000 --- a/build/lib/unit/models/applicationForm.py +++ /dev/null @@ -1,103 +0,0 @@ -import json -from datetime import datetime, date -from typing import Literal, Optional -from unit.utils import date_utils -from unit.models import * - -ApplicationFormStage = Literal["ChooseBusinessOrIndividual", "EnterIndividualInformation", - "IndividualApplicationCreated", "EnterBusinessInformation", "EnterOfficerInformation", - "EnterBeneficialOwnersInformation", "BusinessApplicationCreated", - "EnterSoleProprietorshipInformation", "SoleProprietorshipApplicationCreated"] - - -class ApplicationFormPrefill(object): - def __init__(self, application_type: Optional[str], full_name: Optional[FullName], ssn: Optional[str], - passport: Optional[str], nationality: Optional[str], date_of_birth: Optional[date], - email: Optional[str], name: Optional[str], state_of_incorporation: Optional[str], - entity_type: Optional[str], contact: Optional[BusinessContact], officer: Optional[Officer], - beneficial_owners: [BeneficialOwner], website: Optional[str], dba: Optional[str], - ein: Optional[str], address: Optional[Address], phone: Optional[Phone]): - self.application_type = application_type - self.full_name = full_name - self.ssn = ssn - self.passport = passport - self.nationality = nationality - self.date_of_birth = date_of_birth - self.email = email - self.name = name - self.state_of_incorporation = state_of_incorporation - self.entity_type = entity_type - self.contact = contact - self.officer = officer - self.beneficial_owners = beneficial_owners - self.website = website - self.dba = dba - self.ein = ein - self.address = address - self.phone = phone - - -class ApplicationFormDTO(object): - def __init__(self, id: str, url: str, stage: ApplicationFormStage, applicant_details: ApplicationFormPrefill, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "applicationForm" - self.attributes = {"url": url, "stage": stage, "applicantDetails": applicant_details, "tags": tags} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ApplicationFormDTO(_id, attributes["url"], attributes["stage"], attributes.get("applicantDetails"), - attributes.get("tags"), relationships) - - -AllowedApplicationTypes = Union["Individual", "Business", "SoleProprietorship"] - - -class CreateApplicationFormRequest(UnitRequest): - def __init__(self, tags: Optional[Dict[str, str]] = None, - application_details: Optional[ApplicationFormPrefill] = None, - allowed_application_types: [AllowedApplicationTypes] = None): - self.tags = tags - self.application_details = application_details - self.allowed_application_types = allowed_application_types - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "applicationForm", - "attributes": {} - } - } - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - if self.application_details: - payload["data"]["attributes"]["applicantDetails"] = self.application_details - - if self.allowed_application_types: - payload["data"]["attributes"]["allowedApplicationTypes"] = self.allowed_application_types - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class ListApplicationFormParams(UnitParams): - def __init__(self, offset: int = 0, limit: int = 100, tags: Optional[object] = None, - sort: Optional[Literal["createdAt", "-createdAt"]] = None): - self.offset = offset - self.limit = limit - self.tags = tags - self.sort = sort - - def to_dict(self) -> Dict: - parameters = {"page[limit]": self.limit, "page[offset]": self.offset} - if self.tags: - parameters["filter[tags]"] = self.tags - if self.sort: - parameters["sort"] = self.sort - return parameters - diff --git a/build/lib/unit/models/atm_location.py b/build/lib/unit/models/atm_location.py deleted file mode 100644 index 95d2a545..00000000 --- a/build/lib/unit/models/atm_location.py +++ /dev/null @@ -1,28 +0,0 @@ -import json -from unit.models import * - - -class AtmLocationDTO(object): - def __init__(self, network: int, location_name: str, coordinates: Coordinates, address: Address, distance: int, - surcharge_free: bool, accept_deposits: bool): - self.type = "atmLocation" - self.attributes = {"network": network, "locationName": location_name, "coordinates": coordinates, - "address": address, "distance": distance, "surchargeFree": surcharge_free, - "acceptDeposits": accept_deposits} - - @staticmethod - def from_json_api(_type, attributes): - return AtmLocationDTO(attributes["network"], attributes["locationName"], - Coordinates.from_json_api(attributes["coordinates"]), - Address.from_json_api(attributes["address"]), attributes["distance"], - attributes["surchargeFree"], attributes["acceptDeposits"]) - - -class GetAtmLocationParams(object): - def __init__(self, search_radius: Optional[int] = None, coordinates: Optional[Coordinates] = None, - postal_code: Optional[str] = None, address: Optional[Address] = None): - self.search_radius = search_radius - self.coordinates = coordinates - self.postal_code = postal_code - self.address = address - diff --git a/build/lib/unit/models/authorization.py b/build/lib/unit/models/authorization.py deleted file mode 100644 index 9f6de45b..00000000 --- a/build/lib/unit/models/authorization.py +++ /dev/null @@ -1,61 +0,0 @@ -import json -from typing import Optional -from unit.models import * -from unit.utils import date_utils - -AuthorizationStatus = Literal["Authorized", "Completed", "Canceled", "Declined"] - -class AuthorizationDTO(object): - def __init__(self, id: str, created_at: datetime, amount: int, card_last_4_digits: str, status: AuthorizationStatus, - 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": 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"], - Merchant.from_json_api(attributes["merchant"]), attributes["recurring"], - attributes.get("tags"), relationships) - - -class ListAuthorizationParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, - customer_id: Optional[str] = None, card_id: Optional[str] = None, since: Optional[str] = None, - until: Optional[str] = None, include_non_authorized: Optional[bool] = False, - status: Optional[str] = None, sort: Optional[Literal["createdAt", "-createdAt"]] = None): - self.limit = limit - self.offset = offset - self.account_id = account_id - self.customer_id = customer_id - self.card_id = card_id - self.since = since - self.until = until - self.include_non_authorized = include_non_authorized - self.status = status - self.sort = sort - - 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.account_id: - parameters["filter[accountId]"] = self.account_id - if self.card_id: - parameters["filter[cardId]"] = self.card_id - if self.include_non_authorized: - parameters["filter[includeNonAuthorized]"] = self.include_non_authorized - if self.status: - parameters["filter[status]"] = self.status - if self.since: - parameters["filter[since]"] = self.since - if self.until: - parameters["filter[until]"] = self.until - if self.sort: - parameters["sort"] = self.sort - return parameters diff --git a/build/lib/unit/models/authorization_request.py b/build/lib/unit/models/authorization_request.py deleted file mode 100644 index f654701d..00000000 --- a/build/lib/unit/models/authorization_request.py +++ /dev/null @@ -1,98 +0,0 @@ -import json -from typing import Optional, Literal -from unit.models import * -from unit.utils import date_utils - -PurchaseAuthorizationRequestStatus = Literal["Pending", "Approved", "Declined"] -DeclineReason = Literal["AccountClosed", "CardExceedsAmountLimit", "DoNotHonor", "InsufficientFunds", "InvalidMerchant", - "ReferToCardIssuer", "RestrictedCard", "Timeout", "TransactionNotPermittedToCardholder"] - -class PurchaseAuthorizationRequestDTO(object): - def __init__(self, id: str, created_at: datetime, amount: int, status: PurchaseAuthorizationRequestStatus, - partial_approval_allowed: str, approved_amount: Optional[int], decline_reason: Optional[DeclineReason], - 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]]): - self.id = id - self.type = "purchaseAuthorizationRequest" - self.attributes = {"createdAt": created_at, "amount": amount, "status": status, - "partialApprovalAllowed": partial_approval_allowed, "approvedAmount": approved_amount, - "declineReason": decline_reason, "merchant": { "name": merchant_name, "type": merchant_type, - "category": merchant_category, - "location": merchant_location}, - "recurring": recurring, "tags": tags} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return PurchaseAuthorizationRequestDTO(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["amount"], attributes["status"], - attributes.get("partialApprovalAllowed"), - attributes.get("approvedAmount"), attributes.get("declineReason"), - attributes["merchant"]["name"], attributes["merchant"]["type"], - attributes["merchant"]["category"], - attributes["merchant"].get("location"), attributes["recurring"], - attributes.get("tags"), relationships) - - -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 - self.offset = offset - self.account_id = account_id - self.customer_id = customer_id - - 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.account_id: - parameters["filter[accountId]"] = self.account_id - return parameters - - -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 - self.tags = tags - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "approveAuthorizationRequest", - "attributes": {} - } - } - - if self.amount: - payload["data"]["attributes"]["amount"] = self.amount - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class DeclineAuthorizationRequest(UnitRequest): - def __init__(self, authorization_id: str, reason: DeclineReason): - self.authorization_id = authorization_id - self.reason = reason - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "declineAuthorizationRequest", - "attributes": { - "reason": self.reason - } - } - } - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) diff --git a/build/lib/unit/models/benificial_owner.py b/build/lib/unit/models/benificial_owner.py deleted file mode 100644 index 3b3fcd12..00000000 --- a/build/lib/unit/models/benificial_owner.py +++ /dev/null @@ -1,26 +0,0 @@ -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/build/lib/unit/models/bill_pay.py b/build/lib/unit/models/bill_pay.py deleted file mode 100644 index 23e25713..00000000 --- a/build/lib/unit/models/bill_pay.py +++ /dev/null @@ -1,21 +0,0 @@ -import json -from typing import Optional -from unit.models import * - - -class BillerDTO(object): - def __init__(self, id: str, name: int, category: str): - self.id = id - self.type = "biller" - self.attributes = {"name": name, "category": category} - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return BillerDTO(_id, attributes["name"], attributes["category"]) - - -class GetBillersParams(object): - def __init__(self, name: str, page: Optional[int] = None): - self.name = name - self.page = page - diff --git a/build/lib/unit/models/card.py b/build/lib/unit/models/card.py deleted file mode 100644 index bf0c4ab0..00000000 --- a/build/lib/unit/models/card.py +++ /dev/null @@ -1,617 +0,0 @@ -from unit.utils import date_utils -from unit.models import * - -CardStatus = Literal["Inactive", "Active", "Stolen", "Lost", "Frozen", "ClosedByCustomer", "SuspectedFraud"] - - -class IndividualDebitCardDTO(object): - def __init__(self, id: str, created_at: datetime, last_4_digits: str, expiration_date: str, status: CardStatus, - shipping_address: Optional[Address], design: Optional[str], - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "individualDebitCard" - self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, - "status": status, "shippingAddress": shipping_address, "design": design} - 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 IndividualDebitCardDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], - attributes["expirationDate"], attributes["status"], - shipping_address, attributes.get("design"), relationships - ) - - -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]], 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, - "tags": tags} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - 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"), - 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, - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "individualVirtualDebitCard" - self.attributes = {"createdAt": created_at, "last4Digits": last_4_digits, "expirationDate": expiration_date, - "status": status} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return IndividualVirtualDebitCardDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["last4Digits"], - attributes["expirationDate"], attributes["status"], relationships - ) - - -class BusinessVirtualDebitCardDTO(object): - 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] = 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, - "fullName": full_name, "dateOfBirth": date_of_birth, "address": address, - "phone": phone, "email": email, "status": status, "passport": passport, - "nationality": nationality} - 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"], 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")) - -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)) - -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 - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "individualDebitCard", - "attributes": {}, - "relationships": self.relationships - } - } - - 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 - - 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 CreateBusinessCard(object): - def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - 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.shipping_address = shipping_address - self.ssn = ssn - self.passport = passport - self.nationality = nationality - self.design = design - 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, _type: str) -> Dict: - payload = { - "data": { - "type": _type, - "attributes": { - "fullName": self.full_name, - "dateOfBirth": self.date_of_birth, - "address": self.address, - "phone": self.phone, - "email": self.email, - }, - "relationships": self.relationships - } - } - - if self.shipping_address: - payload["data"]["attributes"]["shippingAddress"] = self.shipping_address - - if self.ssn: - payload["data"]["attributes"]["ssn"] = self.ssn - - if self.passport: - payload["data"]["attributes"]["passport"] = self.passport - - if self.nationality: - payload["data"]["attributes"]["nationality"] = self.nationality - - if self.design: - payload["data"]["attributes"]["design"] = self.design - - if self.idempotency_key: - payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key - - 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): - return json.dumps(self.to_json_api()) - - -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, - limits: Optional[CardLevelLimits] = None, tags: Optional[Dict[str, str]] = None): - self.idempotency_key = idempotency_key - self.limits = limits - self.tags = tags - self.relationships = relationships - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "individualVirtualDebitCard", - "attributes": {}, - "relationships": self.relationships - } - } - - 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 - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class CreateBusinessVirtualCard(object): - def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, - 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.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, _type: str) -> Dict: - payload = { - "data": { - "type": _type, - "attributes": { - "fullName": self.full_name, - "dateOfBirth": self.date_of_birth, - "address": self.address, - "phone": self.phone, - "email": self.email, - }, - "relationships": self.relationships - } - } - - if self.ssn: - payload["data"]["attributes"]["ssn"] = self.ssn - - if self.passport: - payload["data"]["attributes"]["passport"] = self.passport - - if self.nationality: - payload["data"]["attributes"]["nationality"] = self.nationality - - if self.idempotency_key: - payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - if self.limits: - payload["data"]["attributes"]["limits"] = self.limits - - return payload - - def __repr__(self): - 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, CreateBusinessVirtualCreditCard, CreateBusinessCreditCard] - -class PatchIndividualDebitCard(UnitRequest): - def __init__(self,card_id: str, shipping_address: Optional[Address] = None, design: Optional[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: - payload = { - "data": { - "type": "individualDebitCard", - "attributes": {}, - } - } - - 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 - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class PatchBusinessCard(object): - 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, 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, _type: str = "businessDebitCard") -> Dict: - payload = { - "data": { - "type": _type, - "attributes": {}, - } - } - - if self.shipping_address: - payload["data"]["attributes"]["shippingAddress"] = self.shipping_address - - if self.address: - payload["data"]["attributes"]["address"] = self.address - - if self.phone: - payload["data"]["attributes"]["phone"] = self.phone - - if self.email: - payload["data"]["attributes"]["email"] = self.email - - if self.design: - payload["data"]["attributes"]["design"] = self.design - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - if self.limits: - payload["data"]["attributes"]["limits"] = self.limits - - return payload - - def __repr__(self): - return json.dumps(self.to_json_api()) - - -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: - payload = { - "data": { - "type": "individualVirtualDebitCard", - "attributes": {}, - } - } - - 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 PatchBusinessVirtualCard(object): - def __init__(self, card_id: str, address: Optional[Address] = None, phone: Optional[Phone] = 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, _type: str = "businessVirtualDebitCard") -> Dict: - payload = { - "data": { - "type": _type, - "attributes": {}, - } - } - - if self.address: - payload["data"]["attributes"]["address"] = self.address - - if self.phone: - payload["data"]["attributes"]["phone"] = self.phone - - if self.email: - payload["data"]["attributes"]["email"] = self.email - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - if self.limits: - payload["data"]["attributes"]["limits"] = self.limits - - return payload - - -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, PatchBusinessCreditCard, PatchBusinessVirtualCreditCard] - -class ReplaceCardRequest(object): - def __init__(self, shipping_address: Optional[Address] = None): - self.shipping_address = shipping_address - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "replaceCard", - "attributes": {}, - } - } - - if self.shipping_address: - payload["data"]["attributes"]["shippingAddress"] = self.shipping_address - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - -PinStatus = Literal["Set", "NotSet"] - -class PinStatusDTO(object): - def __init__(self, status: PinStatus): - self.type = "pinStatus" - self.attributes = {"status": status} - - @staticmethod - def from_json_api(attributes): - return PinStatusDTO(attributes["status"]) - - -class CardLimitsDTO(object): - def __init__(self, limits: CardLevelLimits, daily_totals: CardTotals, monthly_totals: CardTotals): - self.type = "limits" - self.attributes = {"limits": limits, "dailyTotals": daily_totals, "monthlyTotals": monthly_totals} - - @staticmethod - def from_json_api(attributes): - limits = CardLevelLimits.from_json_api(attributes.get("limits")) if attributes.get("limits") else None - return CardLimitsDTO(limits, CardTotals.from_json_api(attributes.get("dailyTotals")), - CardTotals.from_json_api(attributes.get("monthlyTotals"))) - - -class ListCardParams(UnitParams): - def __init__(self, offset: int = 0, limit: int = 100, account_id: 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} - if self.customer_id: - parameters["filter[customerId]"] = self.customer_id - if self.account_id: - parameters["filter[accountId]"] = self.account_id - if 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/build/lib/unit/models/codecs.py b/build/lib/unit/models/codecs.py deleted file mode 100644 index ed7d296e..00000000 --- a/build/lib/unit/models/codecs.py +++ /dev/null @@ -1,403 +0,0 @@ -import json -from unit.models import * -from datetime import datetime, date - -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 -from unit.models.account import DepositAccountDTO, AccountLimitsDTO -from unit.models.customer import IndividualCustomerDTO, BusinessCustomerDTO -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, BillPaymentDTO, \ - SimulateIncomingAchPaymentDTO -from unit.models.customerToken import CustomerTokenDTO, CustomerVerificationTokenDTO -from unit.models.fee import FeeDTO -from unit.models.event import * -from unit.models.counterparty import CounterpartyDTO, CounterpartyBalanceDTO -from unit.models.webhook import WebhookDTO -from unit.models.institution import InstitutionDTO -from unit.models.statement import StatementDTO -from unit.models.atm_location import AtmLocationDTO -from unit.models.bill_pay import BillerDTO -from unit.models.api_token import APITokenDTO -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: - IndividualApplicationDTO.from_json_api(_id, _type, attributes, relationships), - - "businessApplication": lambda _id, _type, attributes, relationships: - BusinessApplicationDTO.from_json_api(_id, _type, attributes, relationships), - - "document": lambda _id, _type, attributes, relationships: - ApplicationDocumentDTO.from_json_api(_id, _type, attributes), - - "individualCustomer": lambda _id, _type, attributes, relationships: - IndividualCustomerDTO.from_json_api(_id, _type, attributes, relationships), - - "businessCustomer": lambda _id, _type, attributes, relationships: - BusinessCustomerDTO.from_json_api(_id, _type, attributes, relationships), - - "depositAccount": lambda _id, _type, attributes, relationships: - DepositAccountDTO.from_json_api(_id, _type, attributes, relationships), - - "limits": lambda _id, _type, attributes, relationships: - decode_limits(attributes), - - "individualDebitCard": lambda _id, _type, attributes, relationships: - IndividualDebitCardDTO.from_json_api(_id, _type, attributes, relationships), - - "businessDebitCard": lambda _id, _type, attributes, relationships: - BusinessDebitCardDTO.from_json_api(_id, _type, attributes, relationships), - - "individualVirtualDebitCard": lambda _id, _type, attributes, relationships: - IndividualVirtualDebitCardDTO.from_json_api(_id, _type, attributes, relationships), - - "businessVirtualDebitCard": lambda _id, _type, attributes, relationships: - BusinessVirtualDebitCardDTO.from_json_api(_id, _type, attributes, relationships), - - "originatedAchTransaction": lambda _id, _type, attributes, relationships: - OriginatedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "receivedAchTransaction": lambda _id, _type, attributes, relationships: - ReceivedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "returnedAchTransaction": lambda _id, _type, attributes, relationships: - ReturnedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "returnedReceivedAchTransaction": lambda _id, _type, attributes, relationships: - ReturnedReceivedAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "dishonoredAchTransaction": lambda _id, _type, attributes, relationships: - DishonoredAchTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "bookTransaction": lambda _id, _type, attributes, relationships: - BookTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "purchaseTransaction": lambda _id, _type, attributes, relationships: - PurchaseTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "atmTransaction": lambda _id, _type, attributes, relationships: - AtmTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "feeTransaction": lambda _id, _type, attributes, relationships: - FeeTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "cardTransaction": lambda _id, _type, attributes, relationships: - CardTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "wireTransaction": lambda _id, _type, attributes, relationships: - WireTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "releaseTransaction": lambda _id, _type, attributes, relationships: - ReleaseTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "adjustmentTransaction": lambda _id, _type, attributes, relationships: - AdjustmentTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "interestTransaction": lambda _id, _type, attributes, relationships: - InterestTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "disputeTransaction": lambda _id, _type, attributes, relationships: - DisputeTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "checkDepositTransaction": lambda _id, _type, attributes, relationships: - CheckDepositTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "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), - - "repaidPaymentAdvanceTransaction": lambda _id, _type, attributes, relationships: - RepaidPaymentAdvanceTransactionDTO.from_json_api(_id, _type, attributes, relationships), - - "achPayment": lambda _id, _type, attributes, relationships: - AchPaymentDTO.from_json_api(_id, _type, attributes, relationships), - - "bookPayment": lambda _id, _type, attributes, relationships: - BookPaymentDTO.from_json_api(_id, _type, attributes, relationships), - - "wirePayment": lambda _id, _type, attributes, relationships: - WirePaymentDTO.from_json_api(_id, _type, attributes, relationships), - - "billPayment": lambda _id, _type, attributes, relationships: - BillPaymentDTO.from_json_api(_id, _type, attributes, relationships), - - "achReceivedPayment": lambda _id, _type, attributes, relationships: - AchReceivedPaymentDTO.from_json_api(_id, _type, attributes, relationships), - - "accountStatementDTO": lambda _id, _type, attributes, relationships: - StatementDTO.from_json_api(_id, _type, attributes, relationships), - - "sandboxAccountStatement": lambda _id, _type, attributes, relationships: - StatementDTO.from_json_api(_id, _type, attributes, relationships), - - "customerBearerToken": lambda _id, _type, attributes, relationships: - CustomerTokenDTO.from_json_api(_id, _type, attributes, relationships), - - "customerTokenVerification": lambda _id, _type, attributes, relationships: - CustomerVerificationTokenDTO.from_json_api(_id, _type, attributes, relationships), - - "achCounterparty": lambda _id, _type, attributes, relationships: - CounterpartyDTO.from_json_api(_id, _type, attributes, relationships), - - "applicationForm": lambda _id, _type, attributes, relationships: - ApplicationFormDTO.from_json_api(_id, _type, attributes, relationships), - - "fee": lambda _id, _type, attributes, relationships: - FeeDTO.from_json_api(_id, _type, attributes, relationships), - - "account.closed": lambda _id, _type, attributes, relationships: - AccountClosedEvent.from_json_api(_id, _type, attributes, relationships), - - "account.frozen": lambda _id, _type, attributes, relationships: - AccountFrozenEvent.from_json_api(_id, _type, attributes, relationships), - - "application.awaitingDocuments": lambda _id, _type, attributes, relationships: - ApplicationAwaitingDocumentsEvent.from_json_api(_id, _type, attributes, relationships), - - "application.denied": lambda _id, _type, attributes, relationships: - ApplicationDeniedEvent.from_json_api(_id, _type, attributes, relationships), - - "application.pendingReview": lambda _id, _type, attributes, relationships: - ApplicationPendingReviewEvent.from_json_api(_id, _type, attributes, relationships), - - "card.activated": lambda _id, _type, attributes, relationships: - CardActivatedEvent.from_json_api(_id, _type, attributes, relationships), - - "card.statusChanged": lambda _id, _type, attributes, relationships: - CardStatusChangedEvent.from_json_api(_id, _type, attributes, relationships), - - "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), - - "authorizationRequest.pending": lambda _id, _type, attributes, relationships: - AuthorizationRequestPendingEvent.from_json_api(_id, _type, attributes, relationships), - - "authorizationRequest.approved": lambda _id, _type, attributes, relationships: - AuthorizationRequestApprovedEvent.from_json_api(_id, _type, attributes, relationships), - - "document.rejected": lambda _id, _type, attributes, relationships: - DocumentRejectedEvent.from_json_api(_id, _type, attributes, relationships), - - "document.approved": lambda _id, _type, attributes, relationships: - DocumentApprovedEvent.from_json_api(_id, _type, attributes, relationships), - - "checkDeposit.created": lambda _id, _type, attributes, relationships: - CheckDepositCreatedEvent.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), - - "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), - - "transaction.created": lambda _id, _type, attributes, relationships: - TransactionCreatedEvent.from_json_api(_id, _type, attributes, relationships), - - "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), - - "webhook": lambda _id, _type, attributes, relationships: - WebhookDTO.from_json_api(_id, _type, attributes, relationships), - - "institution": lambda _id, _type, attributes, relationships: - InstitutionDTO.from_json_api(_id, _type, attributes, relationships), - - "atmLocation": lambda _id, _type, attributes, relationships: - AtmLocationDTO.from_json_api(_type, attributes), - - "biller": lambda _id, _type, attributes, relationships: - BillerDTO.from_json_api(_id, _type, attributes, relationships), - - "apiToken": lambda _id, _type, attributes, relationships: - APITokenDTO.from_json_api(_id, _type, attributes, relationships), - - "authorization": lambda _id, _type, attributes, relationships: - AuthorizationDTO.from_json_api(_id, _type, attributes, relationships), - - "purchaseAuthorizationRequest": lambda _id, _type, attributes, relationships: - PurchaseAuthorizationRequestDTO.from_json_api(_id, _type, attributes, relationships), - - "accountEndOfDay": lambda _id, _type, attributes, relationships: - AccountEndOfDayDTO.from_json_api(_id, _type, attributes, relationships), - - "counterpartyBalance": lambda _id, _type, attributes, relationships: - CounterpartyBalanceDTO.from_json_api(_id, _type, attributes, relationships), - - "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), - -} - - -def split_json_api_single_response(payload: Dict): - _id, _type, attributes = payload.get("id"), payload["type"], payload["attributes"] - relationships = None - - if payload.get("relationships"): - relationships = dict() - for k, v in payload.get("relationships").items(): - if isinstance(v["data"], list): - # todo: alex handle cases when relationships are in a form of array (e.g. jointAccount or documents) - continue - else: - relationships[k] = Relationship(v["data"]["type"], v["data"]["id"]) - - return _id, _type, attributes, relationships - - -def split_json_api_array_response(payload): - if not isinstance(payload, list): - raise Exception("split_json_api_array_response - couldn't parse response.") - - dtos = [] - for single_obj in payload: - dtos.append(split_json_api_single_response(single_obj)) - - return dtos - - -def decode_limits(attributes: Dict): - if "ach" in attributes.keys(): - return AccountLimitsDTO.from_json_api(attributes) - else: - return CardLimitsDTO.from_json_api(attributes) - -def mapping_wraper(_id, _type, attributes, relationships): - if _type in mappings: - return mappings[_type](_id, _type, attributes, relationships) - else: - return RawUnitObject(_id, _type, attributes, relationships) - -class DtoDecoder(object): - @staticmethod - def decode(payload): - if payload is None: - return None - # if response contains a list of dtos - if isinstance(payload, list): - dtos = split_json_api_array_response(payload) - response = [] - for _id, _type, attributes, relationships in dtos: - response.append(mapping_wraper(_id, _type, attributes, relationships)) - - return response - else: - _id, _type, attributes, relationships = split_json_api_single_response(payload) - return mapping_wraper(_id, _type, attributes, relationships) - -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): - return {"countryCode": obj.country_code, "number": obj.number} - if isinstance(obj, Address): - addr = { - "street": obj.street, - "city": obj.city, - "state": obj.state, - "postalCode": obj.postal_code, - "country": obj.country - } - - if obj.street2 is not None: - addr["street2"] = obj.street2 - return addr - if isinstance(obj, BusinessContact): - return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} - if isinstance(obj, AuthorizedUser): - return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} - 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} - if obj.status is not None: - officer["status"] = obj.status - if obj.title is not None: - officer["title"] = obj.title - if obj.ssn is not None: - officer["ssn"] = obj.ssn - if obj.passport is not None: - officer["passport"] = obj.passport - if obj.nationality is not None: - officer["nationality"] = obj.nationality - return officer - if isinstance(obj, BeneficialOwner): - beneficial_owner = {"fullName": obj.full_name, "dateOfBirth": date_utils.to_date_str(obj.date_of_birth), - "address": obj.address, "phone": obj.phone, "email": obj.email} - if obj.status is not None: - beneficial_owner["status"] = obj.status - if obj.ssn is not None: - beneficial_owner["ssn"] = obj.ssn - if obj.passport is not None: - beneficial_owner["passport"] = obj.passport - if obj.nationality is not None: - beneficial_owner["nationality"] = obj.nationality - if obj.percentage is not None: - beneficial_owner["percentage"] = obj.percentage - return beneficial_owner - if isinstance(obj, RelationshipArray): - return {"data": list(map(lambda r: r.to_dict(), obj.relationships))} - if isinstance(obj, Relationship): - return {"data": obj.to_dict()} - if isinstance(obj, Counterparty): - return {"routingNumber": obj.routing_number, "accountNumber": obj.account_number, - "accountType": obj.account_type, "name": obj.name} - if isinstance(obj, Coordinates): - return {"longitude": obj.longitude, "latitude": obj.latitude} - return json.JSONEncoder.default(self, obj) diff --git a/build/lib/unit/models/counterparty.py b/build/lib/unit/models/counterparty.py deleted file mode 100644 index e043b414..00000000 --- a/build/lib/unit/models/counterparty.py +++ /dev/null @@ -1,172 +0,0 @@ -import json -from datetime import datetime, date -from typing import Optional -from unit.utils import date_utils -from unit.models import * - - -class CounterpartyDTO(object): - def __init__(self, id: str, created_at: datetime, name: str, routing_number: str, bank: Optional[str], - account_number: str, account_type: str, type: str, permissions: str, - relationships: [Dict[str, Relationship]]): - self.id = id - self.type = "achCounterparty" - self.attributes = {"createdAt": created_at, "name": name, "routingNumber": routing_number, "bank": bank, - "accountNumber": account_number, "accountType": account_type, "type": type, - "permissions": permissions} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CounterpartyDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["name"], - attributes["routingNumber"], attributes.get("bank"), attributes["accountNumber"], - attributes["accountType"], attributes["type"], attributes["permissions"], relationships) - - -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): - self.name = name - self.routing_number = routing_number - self.account_number = account_number - self.account_type = account_type - self.type = type - self.relationships = relationships - self.tags = tags - self.idempotency_key = idempotency_key - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "achCounterparty", - "attributes": { - "name": self.name, - "routingNumber": self.routing_number, - "accountNumber": self.account_number, - "accountType": self.account_type, - "type": self.type - }, - "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): - json.dumps(self.to_json_api()) - - -class CreateCounterpartyWithTokenRequest(UnitRequest): - def __init__(self, name: str, type: str, plaid_processor_token: str, relationships: [Dict[str, Relationship]], - verify_name: Optional[bool] = None, permissions: Optional[str] = None, tags: Optional[object] = None, - idempotency_key: Optional[str] = None): - self.name = name - self.type = type - self.plaid_processor_token = plaid_processor_token - self.verify_name = verify_name - self.permissions = permissions - self.relationships = relationships - self.tags = tags - self.idempotency_key = idempotency_key - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "achCounterparty", - "attributes": { - "name": self.name, - "type": self.type, - "plaidProcessorToken": self.plaid_processor_token - }, - "relationships": self.relationships - } - } - - if self.verify_name: - payload["data"]["attributes"]["verifyName"] = self.verify_name - - if self.permissions: - payload["data"]["attributes"]["permissions"] = self.permissions - - 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): - json.dumps(self.to_json_api()) - - -class PatchCounterpartyRequest(object): - def __init__(self, counterparty_id: str, plaid_processor_token: str, verify_name: Optional[bool] = None, - permissions: Optional[str] = None, tags: Optional[object] = None): - self.counterparty_id = counterparty_id - self.plaid_processor_token = plaid_processor_token - self.verify_name = verify_name - self.permissions = permissions - self.tags = tags - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "counterparty", - "attributes": { - "plaidProcessorToken": self.plaid_processor_token - } - } - } - - if self.verify_name: - payload["data"]["attributes"]["verifyName"] = self.verify_name - - if self.permissions: - payload["data"]["attributes"]["permissions"] = self.permissions - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class CounterpartyBalanceDTO(object): - def __init__(self, id: str, balance: int, available: int, relationships: [Dict[str, Relationship]]): - self.id = id - self.type = "counterpartyBalance" - self.attributes = {"balance": balance, "available": available} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CounterpartyBalanceDTO(_id, attributes["balance"], attributes["available"], relationships) - - -class ListCounterpartyParams(UnitParams): - def __init__(self, offset: int = 0, limit: int = 100, customer_id: Optional[str] = None, - tags: Optional[object] = None): - self.offset = offset - self.limit = limit - self.customer_id = customer_id - self.tags = tags - - 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 - return parameters - diff --git a/build/lib/unit/models/customer.py b/build/lib/unit/models/customer.py deleted file mode 100644 index 83ebb29b..00000000 --- a/build/lib/unit/models/customer.py +++ /dev/null @@ -1,158 +0,0 @@ -from unit.utils import date_utils -from unit.models import * - - -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], - authorized_users: [AuthorizedUser], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - 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, "authorizedUsers": authorized_users, "tags": tags} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return IndividualCustomerDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), - 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"), - AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("tags"), relationships - ) - - -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]]): - 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} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return BusinessCustomerDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["name"], - 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(attributes["authorizedUsers"]), - attributes.get("dba"), attributes.get("tags"), relationships) - -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, - 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: - payload = { - "data": { - "type": "individualCustomer", - "attributes": {} - } - } - - if self.address: - payload["data"]["attributes"]["address"] = self.address - - if self.phone: - payload["data"]["attributes"]["phone"] = self.phone - - if self.email: - payload["data"]["attributes"]["email"] = self.email - - 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 - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class PatchBusinessCustomerRequest(UnitRequest): - def __init__(self, customer_id: str, address: Optional[Address] = None, phone: Optional[Phone] = None, - contact: Optional[BusinessContact] = 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.contact = contact - self.authorized_users = authorized_users - self.tags = tags - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "businessCustomer", - "attributes": {} - } - } - - if self.address: - payload["data"]["attributes"]["address"] = self.address - - if self.phone: - payload["data"]["attributes"]["phone"] = self.phone - - if self.contact: - payload["data"]["attributes"]["contact"] = self.contact - - if self.authorized_users: - payload["data"]["attributes"]["authorizedUsers"] = self.authorized_users - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class ListCustomerParams(UnitParams): - def __init__(self, offset: int = 0, limit: int = 100, query: Optional[str] = None, email: Optional[str] = None, - tags: Optional[object] = None, sort: Optional[Literal["createdAt", "-createdAt"]] = None): - self.offset = offset - self.limit = limit - self.query = query - self.email = email - self.tags = tags - self.sort = sort - - def to_dict(self) -> Dict: - parameters = {"page[limit]": self.limit, "page[offset]": self.offset} - if self.query: - parameters["filter[query]"] = self.query - if self.email: - parameters["filter[email]"] = self.email - if self.tags: - parameters["filter[tags]"] = self.tags - if self.sort: - parameters["sort"] = self.sort - return parameters - diff --git a/build/lib/unit/models/customerToken.py b/build/lib/unit/models/customerToken.py deleted file mode 100644 index 856ab64a..00000000 --- a/build/lib/unit/models/customerToken.py +++ /dev/null @@ -1,89 +0,0 @@ -from unit.models import * - -class CustomerTokenDTO(object): - def __init__(self, token: str, expires_in: int): - self.type = "customerBearerToken" - self.attributes = {"token": token, "expiresIn": expires_in} - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CustomerTokenDTO(attributes["token"], attributes["expiresIn"]) - -class CustomerVerificationTokenDTO(object): - def __init__(self, verification_token: str): - self.type = "customerTokenVerification" - self.attributes = {"verificationToken": verification_token} - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CustomerVerificationTokenDTO(attributes["verificationToken"]) - - -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): - self.customer_id = customer_id - self.scope = scope - self.verification_token = verification_token - self.verification_code = verification_code - self.expires_in = expires_in - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "customerToken", - "attributes": { - "scope": self.scope - } - } - } - - if self.expires_in: - payload["data"]["attributes"]["expiresIn"] = self.expires_in - - if self.verification_token: - payload["data"]["attributes"]["verificationToken"] = self.verification_token - - if self.verification_code: - payload["data"]["attributes"]["verificationCode"] = self.verification_code - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class CreateCustomerTokenVerification(UnitRequest): - - def __init__(self, customer_id: str, channel: str, phone: Optional[Phone] = None, app_hash: Optional[str] = None, - language: Optional[str] = None): - self.customer_id = customer_id - self.channel = channel - self.phone = phone - self.app_hash = app_hash - self.language = language - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "customerTokenVerification", - "attributes": { - "channel": self.channel - } - } - } - - if self.phone: - payload["data"]["attributes"]["phone"] = self.phone - - if self.app_hash: - payload["data"]["attributes"]["appHash"] = self.app_hash - - if self.language: - payload["data"]["attributes"]["language"] = self.language - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - diff --git a/build/lib/unit/models/event.py b/build/lib/unit/models/event.py deleted file mode 100644 index e6fcfa72..00000000 --- a/build/lib/unit/models/event.py +++ /dev/null @@ -1,463 +0,0 @@ -import json -from datetime import datetime, date -from typing import Literal, Optional -from unit.utils import date_utils -from unit.models import * - -class BaseEvent(object): - def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.attributes = {"createdAt": created_at, "tags": tags} - self.relationships = relationships - - -class AccountClosedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, close_reason: str, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'account.closed' - self.attributes["closeReason"] = close_reason - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AccountClosedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["closeReason"], - attributes.get("tags"), relationships) - - -class AccountFrozenEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, freeze_reason: str, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'account.frozen' - self.attributes["freezeReason"] = freeze_reason - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AccountFrozenEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["freezeReason"], - attributes.get("tags"), relationships) - - -class ApplicationDeniedEvent(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 = 'application.denied' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ApplicationDeniedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), - relationships) - - -class ApplicationPendingReviewEvent(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 = 'application.pendingReview' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ApplicationPendingReviewEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes.get("tags"), relationships) - - -class ApplicationAwaitingDocumentsEvent(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 = 'application.awaitingDocuments' - - @staticmethod - 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, 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["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, - partial_approval_allowed: str, merchant: Dict[str, str], recurring: str, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'authorizationRequest.approved' - self.attributes["amount"] = amount - self.attributes["status"] = status - self.attributes["approvedAmount"] = approved_amount - self.attributes["partialApprovalAllowed"] = partial_approval_allowed - self.attributes["merchant"] = merchant - self.attributes["recurring"] = recurring - - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AuthorizationRequestApprovedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["amount"], attributes["status"], - attributes["approvedAmount"], attributes["partialApprovalAllowed"], - attributes["merchant"], attributes["recurring"], - attributes.get("tags"), 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, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'authorizationRequest.declined' - self.attributes["amount"] = amount - self.attributes["status"] = status - self.attributes["declineReason"] = decline_reason - self.attributes["partialApprovalAllowed"] = partial_approval_allowed - self.attributes["merchant"] = merchant - self.attributes["recurring"] = recurring - - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AuthorizationRequestDeclinedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["amount"], attributes["status"], - attributes["declineReason"], attributes["partialApprovalAllowed"], - attributes["merchant"], attributes["recurring"], - attributes.get("tags"), relationships) - - -class AuthorizationRequestPendingEvent(BaseEvent): - 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 - - - @staticmethod - def from_json_api(_id, _type, attributes, 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]], - relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'card.activated' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CardActivatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), - relationships) - - -class CardStatusChangedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, new_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 = 'card.statusChanged' - self.attributes["newStatus"] = new_status - self.attributes["previousStatus"] = previous_status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CardStatusChangedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["newStatus"], - attributes["previousStatus"], 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]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'checkDeposit.created' - self.attributes["status"] = status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["status"], 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]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'checkDeposit.clearing' - self.attributes["previousStatus"] = previous_status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CheckDepositClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["previousStatus"], attributes.get("tags"), relationships) - - -class CheckDepositSentEvent(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.sent' - self.attributes["previousStatus"] = previous_status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CheckDepositSentEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["previousStatus"], 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]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'checkDeposit.returned' - self.attributes["previousStatus"] = previous_status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CheckDepositReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["previousStatus"], 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]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'customer.created' - - @staticmethod - 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]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'document.approved' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return DocumentApprovedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes.get("tags"), relationships) - -class DocumentRejectedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, reason: str, reason_code: str, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'document.rejected' - self.attributes["reason"] = reason - self.attributes["reasonCode"] = reason_code - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return DocumentRejectedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - 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]], - relationships: Optional[Dict[str, Relationship]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'payment.clearing' - self.attributes["previousStatus"] = previous_status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return PaymentClearingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], - attributes.get("tags"), relationships) - -class PaymentSentEvent(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 = 'payment.sent' - self.attributes["previousStatus"] = previous_status - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return PaymentSentEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], - attributes.get("tags"), relationships) - -class PaymentReturnedEvent(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 = 'payment.returned' - self.attributes["previousStatus"] = previous_status - - @staticmethod - 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]]): - BaseEvent.__init__(self, id, created_at, tags, relationships) - self.type = 'statements.created' - self.attributes["period"] = period - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return StatementsCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["period"], - attributes.get("tags"), relationships) - -class TransactionCreatedEvent(BaseEvent): - 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' - self.attributes["summary"] = summary - self.attributes["direction"] = direction - self.attributes["amount"] = amount - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return TransactionCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["summary"], - attributes["direction"], attributes["amount"], attributes.get("tags"), - relationships) - -class AccountReopenedEvent(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 = 'account.reopened' - - @staticmethod - 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, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, - AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, - AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, - CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, - CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, - PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject] - - -class ListEventParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, type: Optional[List[str]] = None): - self.limit = limit - self.offset = offset - self.type = type - - def to_dict(self) -> Dict: - parameters = {"page[limit]": self.limit, "page[offset]": self.offset} - if self.type: - parameters["filter[type][]"] = self.type - return parameters diff --git a/build/lib/unit/models/fee.py b/build/lib/unit/models/fee.py deleted file mode 100644 index 296ce187..00000000 --- a/build/lib/unit/models/fee.py +++ /dev/null @@ -1,50 +0,0 @@ -import json -from typing import Optional -from unit.models import * - - -class FeeDTO(object): - def __init__(self, id: str, amount: int, description: str, tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = "fee" - self.attributes = {"amount": amount, "description": description, "tags": tags} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return FeeDTO(_id, attributes["amount"], attributes["description"], attributes.get("tags"), relationships) - - -class CreateFeeRequest(object): - def __init__(self, amount: int, description: str, relationships: Optional[Dict[str, Relationship]], - tags: Optional[Dict[str, str]] = None, idempotency_key: Optional[str] = None): - self.amount = amount - self.description = description - self.tags = tags - self.idempotency_key = idempotency_key - self.relationships = relationships - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "fee", - "attributes": { - "amount": self.amount, - "description": self.description - }, - "relationships": self.relationships - } - } - - if self.idempotency_key: - payload["data"]["attributes"]["idempotencyKey"] = self.tags - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - diff --git a/build/lib/unit/models/institution.py b/build/lib/unit/models/institution.py deleted file mode 100644 index 25599e68..00000000 --- a/build/lib/unit/models/institution.py +++ /dev/null @@ -1,17 +0,0 @@ -import json -from typing import Optional -from unit.models import * - - -class InstitutionDTO(object): - def __init__(self, routing_number: str, name: str, is_ach_supported: bool, is_wire_supported: bool, - address: Optional[Address] = None): - self.type = "institution" - self.attributes = {"routingNumber": routing_number, "name": name, "address": address, - "isACHSupported": is_ach_supported, "isWireSupported": is_wire_supported} - - def from_json_api(_id, _type, attributes, relationships): - return InstitutionDTO( - attributes["routingNumber"], attributes["name"], attributes["isACHSupported"], - attributes["isWireSupported"], attributes.get("address")) - diff --git a/build/lib/unit/models/payment.py b/build/lib/unit/models/payment.py deleted file mode 100644 index 502fb3f0..00000000 --- a/build/lib/unit/models/payment.py +++ /dev/null @@ -1,450 +0,0 @@ -from unit.utils import date_utils -from unit.models import * - -PaymentTypes = Literal["AchPayment", "BookPayment", "WirePayment", "BillPayment"] -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]]): - self.id = id - self.attributes = {"createdAt": created_at, "status": status, "direction": direction, - "description": description, "amount": amount, "reason": reason, "tags": tags} - self.relationships = relationships - -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], - settlement_date: Optional[datetime], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - BasePayment.__init__(self, id, created_at, status, direction, description, amount, reason, tags, relationships) - self.type = 'achPayment' - self.attributes["counterparty"] = counterparty - self.attributes["addenda"] = addenda - self.settlement_date = settlement_date - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - settlement_date = date_utils.to_date(attributes.get("settlementDate")) if attributes.get("settlementDate") else None - return AchPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], - attributes["counterparty"], attributes["direction"], attributes["description"], - 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]], - relationships: Optional[Dict[str, Relationship]]): - BasePayment.__init__(self, id, created_at, status, direction, description, amount, reason, tags, relationships) - self.type = 'bookPayment' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return BookPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], - attributes.get("direction"), attributes["description"], attributes["amount"], - attributes.get("reason"), attributes.get("tags"), relationships) - -class WirePaymentDTO(BasePayment): - def __init__(self, id: str, created_at: datetime, status: PaymentStatus, counterparty: WireCounterparty, - direction: str, description: str, amount: int, reason: Optional[str], tags: Optional[Dict[str, str]], - relationships: Optional[Dict[str, Relationship]]): - BasePayment.__init__(self, id, created_at, direction, description, amount, reason, tags, relationships) - self.type = "wirePayment" - self.attributes["counterparty"] = counterparty - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return WirePaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], - WireCounterparty.from_json_api(attributes["counterparty"]), attributes["direction"], - attributes["description"], attributes["amount"], 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, tags, relationships) - self.type = 'billPayment' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return BillPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], - attributes["direction"], attributes["description"], attributes["amount"], - attributes.get("reason"), attributes.get("tags"), relationships) - -PaymentDTO = Union[AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, BillPaymentDTO] - -AchReceivedPaymentStatus = Literal["Pending", "Advanced", "Completed", "Returned"] - -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]]): - 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} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AchReceivedPaymentDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], - attributes["wasAdvanced"], attributes["completionDate"], - 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) - -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"): - self.type = type - self.amount = amount - self.description = description - self.direction = direction - self.idempotency_key = idempotency_key - self.tags = tags - self.relationships = relationships - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": self.type, - "attributes": { - "amount": self.amount, - "direction": self.direction, - "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 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) - self.counterparty = counterparty - self.addenda = addenda - - def to_json_api(self) -> Dict: - payload = CreatePaymentBaseRequest.to_json_api(self) - - payload["data"]["attributes"]["counterparty"] = self.counterparty - - if self.addenda: - payload["data"]["attributes"]["addenda"] = self.addenda - - return payload - -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) - self.addenda = addenda - self.verify_counterparty_balance = verify_counterparty_balance - - def to_json_api(self) -> Dict: - payload = CreatePaymentBaseRequest.to_json_api(self) - - if self.addenda: - payload["data"]["attributes"]["addenda"] = self.addenda - - if self.verify_counterparty_balance: - payload["data"]["attributes"]["verifyCounterpartyBalance"] = self.verify_counterparty_balance - - 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], - idempotency_key: Optional[str], tags: Optional[Dict[str, str]], direction: str = "Credit"): - CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags) - self.plaid_Processor_token = plaid_Processor_token - self.counterparty_name = counterparty_name - self.verify_counterparty_balance = verify_counterparty_balance - - def to_json_api(self) -> Dict: - payload = CreatePaymentBaseRequest.to_json_api(self) - payload["data"]["attributes"]["counterparty"] = self.counterparty - payload["data"]["attributes"]["plaidProcessorToken"] = self.plaid_processor_token - - if counterparty_name: - payload["data"]["attributes"]["counterpartyName"] = self.counterparty_name - - if verify_counterparty_balance: - payload["data"]["attributes"]["verifyCounterpartyBalance"] = self.verify_counterparty_balance - - return payload - -class CreateBookPaymentRequest(CreatePaymentBaseRequest): - def __init__(self, amount: int, description: str, relationships: Dict[str, Relationship], - idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None, - direction: str = "Credit"): - super().__init__(amount, description, relationships, idempotency_key, tags, direction, "bookPayment") - -class CreateWirePaymentRequest(CreatePaymentBaseRequest): - def __init__(self, amount: int, description: str, counterparty: WireCounterparty, - relationships: Dict[str, Relationship], idempotency_key: Optional[str], tags: Optional[Dict[str, str]], - direction: str = "Credit"): - CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, - "wirePayment") - self.counterparty = counterparty - - def to_json_api(self) -> Dict: - payload = CreatePaymentBaseRequest.to_json_api(self) - payload["data"]["attributes"]["counterparty"] = self.counterparty - return payload - -CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, - CreateBookPaymentRequest, CreateWirePaymentRequest] - -class PatchAchPaymentRequest(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": "achPayment", - "attributes": { - "tags": self.tags - } - } - } - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - -class PatchBookPaymentRequest(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": "bookPayment", - "attributes": { - "tags": self.tags - } - } - } - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - -PatchPaymentRequest = Union[PatchAchPaymentRequest, PatchBookPaymentRequest] - -class ListPaymentParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, - customer_id: Optional[str] = None, tags: Optional[object] = None, - status: Optional[List[PaymentStatus]] = None, type: Optional[List[PaymentTypes]] = None, - direction: Optional[List[PaymentDirections]] = None, since: Optional[str] = None, - until: Optional[str] = None, sort: Optional[Literal["createdAt", "-createdAt"]] = None, - include: Optional[str] = None): - self.limit = limit - self.offset = offset - self.account_id = account_id - self.customer_id = customer_id - self.tags = tags - self.status = status - self.type = type - self.direction = direction - 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.customer_id: - parameters["filter[customerId]"] = self.customer_id - if self.account_id: - parameters["filter[accountId]"] = self.account_id - if self.tags: - parameters["filter[tags]"] = self.tags - 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 - if self.direction: - for idx, direction_filter in enumerate(self.direction): - parameters[f"filter[direction][{idx}]"] = direction_filter - if self.since: - parameters["filter[since]"] = self.since - if self.until: - parameters["filter[until]"] = self.until - if self.sort: - parameters["sort"] = self.sort - if self.include: - parameters["include"] = self.include - return parameters - -class ListReceivedPaymentParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, - customer_id: Optional[str] = None, tags: Optional[object] = None, - status: Optional[List[AchReceivedPaymentStatus]] = None, - direction: Optional[List[PaymentDirections]] = None, include_completed: Optional[bool] = None, - sort: Optional[Literal["createdAt", "-createdAt"]] = None, include: Optional[str] = None): - self.limit = limit - self.offset = offset - self.account_id = account_id - self.customer_id = customer_id - self.tags = tags - self.status = status - self.include_completed = include_completed - self.sort = sort - self.include = include - - 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.account_id: - parameters["filter[accountId]"] = self.account_id - if self.tags: - parameters["filter[tags]"] = self.tags - if self.include_completed: - parameters["filter[includeCompleted]"] = self.include_completed - if self.status: - for idx, status_filter in enumerate(self.status): - parameters[f"filter[status][{idx}]"] = status_filter - if self.sort: - parameters["sort"] = self.sort - if self.include: - parameters["include"] = self.include - return parameters diff --git a/build/lib/unit/models/repayment.py b/build/lib/unit/models/repayment.py deleted file mode 100644 index 3554b94b..00000000 --- a/build/lib/unit/models/repayment.py +++ /dev/null @@ -1,114 +0,0 @@ -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/build/lib/unit/models/returnAch.py b/build/lib/unit/models/returnAch.py deleted file mode 100644 index 6c972246..00000000 --- a/build/lib/unit/models/returnAch.py +++ /dev/null @@ -1,29 +0,0 @@ -import json -from typing import Literal -from unit.models import * - -AchReturnReason = Literal["InsufficientFunds", "Unauthorized", "UncollectedFunds"] - - -class ReturnReceivedAchTransactionRequest(UnitRequest): - def __init__(self, transaction_id: str, reason: AchReturnReason, relationships: [Dict[str, Relationship]]): - self.transaction_id = transaction_id - self.reason = reason - self.relationships = relationships - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "returnAch", - "attributes": { - "reason": self.reason - }, - "relationships": self.relationships - } - } - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - diff --git a/build/lib/unit/models/reward.py b/build/lib/unit/models/reward.py deleted file mode 100644 index 07f1535b..00000000 --- a/build/lib/unit/models/reward.py +++ /dev/null @@ -1,137 +0,0 @@ -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/build/lib/unit/models/statement.py b/build/lib/unit/models/statement.py deleted file mode 100644 index 590e2604..00000000 --- a/build/lib/unit/models/statement.py +++ /dev/null @@ -1,46 +0,0 @@ -import json -from unit.models import * - - -class StatementDTO(object): - def __init__(self, id: str, _type: str, period: str, relationships: Optional[Dict[str, Relationship]]): - self.id = id - self.type = _type - self.attributes = {"period": period} - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return StatementDTO(_id, _type, attributes["period"], relationships) - - -OutputType = Literal["html", "pdf"] - -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 - self.output_type = output_type - self.language = language - self.customer_id = customer_id - - -class ListStatementParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, customer_id: Optional[str] = None, - account_id: Optional[str] = None, sort: Optional[Literal["period", "-period"]] = None): - self.limit = limit - self.offset = offset - self.customer_id = customer_id - self.account_id = account_id - self.sort = sort - - 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.account_id: - parameters["filter[accountId]"] = self.account_id - if self.sort: - parameters["sort"] = self.sort - return parameters - diff --git a/build/lib/unit/models/transaction.py b/build/lib/unit/models/transaction.py deleted file mode 100644 index 58aec0fa..00000000 --- a/build/lib/unit/models/transaction.py +++ /dev/null @@ -1,466 +0,0 @@ -from unit.utils import date_utils -from unit.models import * - - -class BaseTransactionDTO(object): - 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]]): - self.id = id - self.attributes = {"createdAt": created_at, "direction": direction, "amount": amount, "balance": balance, - "summary": summary, "tags": tags} - self.relationships = relationships - - -class OriginatedAchTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, description: str, addenda: Optional[str], counterparty: Counterparty, - tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]]): - BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) - self.type = 'originatedAchTransaction' - self.attributes["description"] = description - self.attributes["addenda"] = addenda - self.attributes["counterparty"] = counterparty - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return OriginatedAchTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], attributes["description"], - attributes.get("addenda"), Counterparty.from_json_api(attributes["counterparty"]), - attributes.get("tags"), relationships) - - -class ReceivedAchTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, description: str, addenda: Optional[str], company_name: str, - counterparty_routing_number: str, trace_number: Optional[str], sec_code: 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 = 'receivedAchTransaction' - self.attributes["description"] = description - self.attributes["addenda"] = addenda - self.attributes["companyName"] = company_name - self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number - self.attributes["traceNumber"] = trace_number - self.attributes["secCode"] = sec_code - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ReceivedAchTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], attributes["description"], - attributes.get("addenda"), attributes["companyName"], attributes["counterpartyRoutingNumber"], - attributes.get("traceNumber"), attributes.get("secCode"), attributes.get("tags"), relationships) - - -class ReturnedAchTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, company_name: str, counterparty_name: str, counterparty_routing_number: str, 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 = 'returnedAchTransaction' - self.attributes["companyName"] = company_name - self.attributes["counterpartyName"] = counterparty_name - self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number - self.attributes["reason"] = reason - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ReturnedAchTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], attributes["amount"], - attributes["balance"], attributes["summary"], attributes["companyName"], attributes["counterpartyName"], - attributes["counterpartyRoutingNumber"], attributes["reason"], attributes.get("tags"), relationships) - - -class ReturnedReceivedAchTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, - company_name: str, 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 = 'returnedReceivedAchTransaction' - self.attributes["companyName"] = company_name - self.attributes["reason"] = reason - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ReturnedReceivedAchTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], attributes["companyName"], - attributes["reason"], attributes.get("tags"), relationships) - - -class DishonoredAchTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, - company_name: str, counterparty_routing_number: str, reason: str, trace_number: Optional[str], - sec_code: 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 = 'dishonoredAchTransaction' - self.attributes["companyName"] = company_name - self.attributes["counterpartyRoutingNumber"] = counterparty_routing_number - self.attributes["traceNumber"] = trace_number - self.attributes["reason"] = reason - self.attributes["secCode"] = sec_code - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return DishonoredAchTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], attributes["companyName"], - attributes["counterpartyRoutingNumber"], attributes["reason"], attributes.get("traceNumber"), - attributes.get("secCode"), attributes.get("tags"), 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]] = 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 - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return BookTransactionDTO( - 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]]): - 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 - self.attributes["coordinates"] = coordinates - self.attributes["recurring"] = recurring - self.attributes["interchange"] = interchange - self.attributes["ecommerce"] = ecommerce - self.attributes["cardPresent"] = card_present - self.attributes["paymentMethod"] = payment_method - self.attributes["digitalWallet"] = digital_wallet - self.attributes["cardVerificationData"] = card_verification_data - self.attributes["cardNetwork"] = card_network - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - 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.get("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) - - -class AtmTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, card_last_4_digits: str, atm_name: str, atm_location: Optional[str], surcharge: int, - interchange: Optional[int], 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 = 'atmTransaction' - self.attributes["cardLast4Digits"] = card_last_4_digits - self.attributes["atmName"] = atm_name - self.attributes["atmLocation"] = atm_location - self.attributes["surcharge"] = surcharge - self.attributes["interchange"] = interchange - self.attributes["cardNetwork"] = card_network - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AtmTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes["cardLast4Digits"], attributes["atmName"], attributes.get("atmLocation"), - attributes["surcharge"], attributes.get("interchange"), attributes.get("cardNetwork"), - attributes.get("tags"), relationships) - - -class FeeTransactionDTO(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 = 'feeTransaction' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return FeeTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes.get("tags"), 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], - 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 - self.attributes["recurring"] = recurring - self.attributes["interchange"] = interchange - self.attributes["paymentMethod"] = payment_method - self.attributes["digitalWallet"] = digital_wallet - self.attributes["cardVerificationData"] = card_verification_data - self.attributes["cardNetwork"] = card_network - - - @staticmethod - 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.get("recurring"), attributes.get("interchange"), - attributes.get("paymentMethod"), attributes.get("digitalWallet"), - attributes.get("cardVerificationData"), attributes.get("cardNetwork"), - attributes.get("tags"), relationships) - - -class CardReversalTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, card_last_4_digits: 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 = 'cardReversalTransaction' - self.attributes["cardLast4Digits"] = card_last_4_digits - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CardReversalTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes["cardLast4Digits"], attributes.get("tags"), relationships) - - -class WireTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, counterparty: Counterparty, description: str, - originator_to_beneficiary_information: str, sender_reference: str, - reference_for_beneficiary: str, beneficiary_information: str, - beneficiary_advice_information: 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 = 'wireTransaction' - self.attributes["description"] = description - self.attributes["counterparty"] = counterparty - self.attributes["originatorToBeneficiaryInformation"] = originator_to_beneficiary_information - self.attributes["senderReference"] = sender_reference - self.attributes["referenceForBeneficiary"] = reference_for_beneficiary - self.attributes["beneficiaryInformation"] = beneficiary_information - self.attributes["beneficiaryAdviceInformation"] = beneficiary_advice_information - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return WireTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - Counterparty.from_json_api(attributes["counterparty"]), attributes["description"], - attributes.get("originatorToBeneficiaryInformation"), attributes.get("senderReference"), - attributes.get("referenceForBeneficiary"), attributes.get("beneficiaryInformation"), - attributes.get("beneficiaryAdviceInformation"), attributes.get("tags"), relationships) - - -class ReleaseTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, sender_name: str, sender_address: Address, - sender_account_number: str, counterparty: Counterparty, amount: int, direction: str, - description: 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 = 'releaseTransaction' - self.attributes["description"] = description - self.attributes["senderName"] = sender_name - self.attributes["senderAddress"] = sender_address - self.attributes["senderAccountNumber"] = sender_account_number - self.attributes["counterparty"] = counterparty - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ReleaseTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["senderName"], - Address.from_json_api(attributes["senderAddress"]), - attributes["senderAccountNumber"], - Counterparty.from_json_api(attributes["counterparty"]), attributes["amount"], - attributes["direction"], attributes["description"], attributes["balance"], - attributes["summary"], attributes.get("tags"), relationships) - - -class AdjustmentTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, summary: str, - description: 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 = 'adjustmentTransaction' - self.attributes["description"] = description - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return AdjustmentTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], - attributes["summary"], attributes["description"], attributes.get("tags"), - relationships) - - -class InterestTransactionDTO(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 = 'interestTransaction' - self.relationships = relationships - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return InterestTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes.get("tags"), relationships) - - -class DisputeTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, dispute_id: str, - summary: str, 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 = 'disputeTransaction' - self.attributes["disputeId"] = dispute_id - self.attributes["reason"] = reason - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return DisputeTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["disputeId"], - attributes["summary"], attributes["reason"], attributes.get("tags"), relationships) - - -class CheckDepositTransactionDTO(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 = 'checkDepositTransaction' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return CheckDepositTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes.get("tags"), relationships) - - -class ReturnedCheckDepositTransactionDTO(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]]): - BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) - self.type = 'returnedCheckDepositTransaction' - self.attributes["reason"] = reason - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return ReturnedCheckDepositTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes["reason"], 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]]): - BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) - self.type = 'paymentAdvanceTransaction' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return PaymentAdvanceTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["direction"], - attributes["amount"], attributes["balance"], attributes["summary"], - attributes.get("tags"), relationships) - -class RepaidPaymentAdvanceTransactionDTO(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]]): - BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) - self.type = 'repaidPaymentAdvanceTransaction' - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return RepaidPaymentAdvanceTransactionDTO(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["direction"], - attributes["amount"], 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] - - -class PatchTransactionRequest(BaseTransactionDTO, UnitRequest): - def __init__(self, account_id: str, transaction_id: str, tags: Optional[Dict[str, str]] = None): - self.account_id = account_id - self.transaction_id = transaction_id - self.tags = tags - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "transaction", - "attributes": {} - } - } - - if self.tags: - payload["data"]["attributes"]["tags"] = self.tags - - return payload - - -class ListTransactionParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0, account_id: Optional[str] = None, - customer_id: Optional[str] = None, query: Optional[str] = None, tags: Optional[object] = None, - since: Optional[str] = None, until: Optional[str] = None, card_id: Optional[str] = None, - type: Optional[List[str]] = None, exclude_fees: Optional[bool] = None, - sort: Optional[Literal["createdAt", "-createdAt"]] = None, include: Optional[str] = None): - self.limit = limit - self.offset = offset - self.account_id = account_id - self.customer_id = customer_id - self.query = query - self.tags = tags - self.since = since - self.until = until - self.card_id = card_id - self.type = type - self.exclude_fees = exclude_fees - self.sort = sort - self.include = include - - 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.account_id: - parameters["filter[accountId]"] = self.account_id - if self.query: - parameters["filter[query]"] = self.query - if self.tags: - parameters["filter[tags]"] = self.tags - if self.since: - parameters["filter[since]"] = self.since - if self.until: - parameters["filter[until]"] = self.until - if self.card_id: - parameters["filter[cardId]"] = self.card_id - if self.type: - for idx, type_filter in enumerate(self.type): - parameters[f"filter[type][{idx}]"] = type_filter - if self.exclude_fees: - parameters["filter[excludeFees]"] = self.exclude_fees - if self.sort: - parameters["sort"] = self.sort - if self.include: - parameters["include"] = self.include - return parameters \ No newline at end of file diff --git a/build/lib/unit/models/webhook.py b/build/lib/unit/models/webhook.py deleted file mode 100644 index c19d82c1..00000000 --- a/build/lib/unit/models/webhook.py +++ /dev/null @@ -1,92 +0,0 @@ -import json -from datetime import datetime, date -from unit.utils import date_utils -from unit.models import * -from typing import Literal - -ContentType = Literal["Json", "JsonAPI"] -WebhookStatus = Literal["Enabled", "Disabled"] - - -class WebhookDTO(object): - def __init__(self, id: str, created_at: datetime, label: str, url: str, status: WebhookStatus, - content_type: ContentType, token: str): - self.id = id - self.type = 'webhook' - self.attributes = {"createdAt": created_at, "label": label, "url": url, "status": status, - "contentType": content_type, "token": token} - - @staticmethod - def from_json_api(_id, _type, attributes, relationships): - return WebhookDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["label"], attributes["url"], - attributes["status"], attributes["contentType"], attributes["token"]) - - -class CreateWebhookRequest(object): - def __init__(self, label: str, url: str, token: str, content_type: ContentType): - self.label = label - self.url = url - self.token = token - self.content_type = content_type - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "webhook", - "attributes": { - "label": self.label, - "url": self.url, - "token": self.token, - "contentType": self.content_type - } - } - } - - return payload - - def __repr__(self): - json.dumps(self.to_json_api()) - - -class PatchWebhookRequest(object): - def __init__(self, webhook_id: str, label: Optional[str] = None, url: Optional[str] = None, - content_type: Optional[ContentType] = None, token: Optional[str] = None): - self.webhook_id = webhook_id - self.label = label - self.url = url - self.content_type = content_type - self.token = token - - def to_json_api(self) -> Dict: - payload = { - "data": { - "type": "webhook", - "attributes": {} - } - } - - if self.label: - payload["data"]["attributes"]["label"] = self.label - - if self.url: - payload["data"]["attributes"]["url"] = self.url - - if self.content_type: - payload["data"]["attributes"]["contentType"] = self.content_type - - if self.token: - payload["data"]["attributes"]["token"] = self.token - - return payload - - -class ListWebhookParams(UnitParams): - def __init__(self, limit: int = 100, offset: int = 0): - self.limit = limit - self.offset = offset - - def to_dict(self) -> Dict: - parameters = {"page[limit]": self.limit, "page[offset]": self.offset} - return parameters - diff --git a/build/lib/unit/utils/__init__.py b/build/lib/unit/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/build/lib/unit/utils/date_utils.py b/build/lib/unit/utils/date_utils.py deleted file mode 100644 index 6dbdbec6..00000000 --- a/build/lib/unit/utils/date_utils.py +++ /dev/null @@ -1,13 +0,0 @@ -from datetime import date, datetime - - -def to_datetime(dt: str): - return datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%f%z") - - -def to_date(d: str): - return date.fromisoformat(d) - - -def to_date_str(d: date): - return d.strftime("%Y-%m-%d") From a27740c5328fddab92e126161d437204ae6b2a06 Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Thu, 20 Jul 2023 12:33:59 -0400 Subject: [PATCH 077/181] Minor fixes to charge cards --- unit/__init__.py | 2 + unit/api/repayment_resource.py | 16 ++- unit/models/__init__.py | 230 +++++++++++++++++++++++++++------ unit/models/card.py | 8 +- 4 files changed, 210 insertions(+), 46 deletions(-) diff --git a/unit/__init__.py b/unit/__init__.py index 99bda338..bc74f256 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -4,6 +4,7 @@ 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 @@ -33,6 +34,7 @@ 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.repayments = RepaymentResource(api_url, token) self.ach = AchResource(api_url, token) self.statements = StatementResource(api_url, token) self.customerTokens = CustomerTokenResource(api_url, token) diff --git a/unit/api/repayment_resource.py b/unit/api/repayment_resource.py index d55a69d0..74276816 100644 --- a/unit/api/repayment_resource.py +++ b/unit/api/repayment_resource.py @@ -3,7 +3,11 @@ 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 +from unit.models.repayment import ( + RepaymentDTO, + CreateRepaymentRequest, + ListRepaymentParams, +) class RepaymentResource(BaseResource): @@ -11,9 +15,11 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "repayments" - def create(self, request: CreateRepaymentRequest) -> Union[UnitResponse[RepaymentDTO], UnitError]: + def create( + self, request: CreateRepaymentRequest + ) -> Union[UnitResponse[RepaymentDTO], UnitError]: payload = request.to_json_api() - response = super().post_create(self.resource, payload) + 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) @@ -28,7 +34,9 @@ def get(self, repayment_id: str) -> Union[UnitResponse[RepaymentDTO], UnitError] else: return UnitError.from_json_api(response.json()) - def list(self, params: Optional[ListRepaymentParams] = None) -> Union[UnitResponse[List[RepaymentDTO]], UnitError]: + 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): diff --git a/unit/models/__init__.py b/unit/models/__init__.py index a25d06dc..dfcf1f47 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -2,11 +2,12 @@ from typing import TypeVar, Generic, Union, Optional, Literal, List, Dict from datetime import datetime, date + def to_camel_case(snake_str): - components = snake_str.lstrip('_').split('_') + 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:]) + return components[0] + "".join(x.title() for x in components[1:]) def extract_attributes(list_of_attributes, attributes): @@ -24,7 +25,10 @@ def to_dict(self): return self else: v = vars(self) - return dict((to_camel_case(k), val) for k, val in v.items() if val is not None) + 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): @@ -35,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]): @@ -56,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 @@ -67,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 @@ -89,21 +132,39 @@ 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 + ] + } + ) Status = Literal["Approved", "Denied", "PendingReview"] Title = Literal["CEO", "COO", "CFO", "President"] EntityType = Literal["Corporation", "LLC", "Partnership"] + class FullName(object): def __init__(self, first: str, last: str): self.first = first @@ -119,8 +180,15 @@ def from_json_api(data: Dict): # 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): + 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 @@ -130,8 +198,14 @@ 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): @@ -152,13 +226,27 @@ 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): - 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): + 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, + ): self.full_name = full_name self.date_of_birth = date_of_birth self.address = address @@ -172,15 +260,34 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p @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")) + 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): + 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 @@ -196,9 +303,20 @@ def __init__(self, full_name: FullName, date_of_birth: date, address: Address, p 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"), + ) + ) return beneficial_owners @@ -212,11 +330,18 @@ def __init__(self, full_name: FullName, email: str, phone: Phone): 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") + ) + ) return authorized_users + class WireCounterparty(object): - def __init__(self, routing_number: str, account_number: str, name: str, address: Address): + 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 @@ -224,11 +349,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"])) + 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): + 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 @@ -236,7 +368,13 @@ 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 Coordinates(object): def __init__(self, longitude: int, latitude: int): @@ -252,7 +390,9 @@ def from_json_api(data: Dict): class Merchant(object): - def __init__(self, name: str, type: int, category: Optional[str], location: Optional[str]): + def __init__( + self, name: str, type: int, category: Optional[str], location: Optional[str] + ): self.name = name self.type = type self.category = category @@ -260,10 +400,19 @@ def __init__(self, name: str, type: int, category: Optional[str], location: Opti @staticmethod def from_json_api(data: Dict): - return Merchant(data["name"], data["type"], data.get("category"), data.get("location")) + return Merchant( + data["name"], data["type"], data.get("category"), data.get("location") + ) + class CardLevelLimits(object): - def __init__(self, daily_withdrawal: int, daily_purchase: int, monthly_withdrawal: int, monthly_purchase: int): + 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 @@ -271,8 +420,13 @@ 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): def __init__(self, withdrawals: int, deposits: int, purchases: int): diff --git a/unit/models/card.py b/unit/models/card.py index bf0c4ab0..c8ed53f8 100644 --- a/unit/models/card.py +++ b/unit/models/card.py @@ -190,7 +190,7 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessCard(object): +class CreateBusinessCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, relationships: Dict[str, Relationship], shipping_address: Optional[Address] = None, ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, @@ -306,7 +306,7 @@ def __repr__(self): json.dumps(self.to_json_api()) -class CreateBusinessVirtualCard(object): +class CreateBusinessVirtualCard(UnitRequest): def __init__(self, full_name: FullName, date_of_birth: date, address: Address, phone: Phone, email: str, relationships: Dict[str, Relationship], ssn: Optional[str] = None, passport: Optional[str] = None, nationality: Optional[str] = None, idempotency_key: Optional[str] = None, @@ -411,7 +411,7 @@ def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessCard(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, limits: Optional[CardLevelLimits] = None): @@ -491,7 +491,7 @@ def to_json_api(self) -> Dict: def __repr__(self): json.dumps(self.to_json_api()) -class PatchBusinessVirtualCard(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, _type: str = "businessVirtualDebitCard", limits: Optional[CardLevelLimits] = None): From c997b22384fd2c8622b9048196ff0a27e1a6a369 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 20 Jul 2023 11:11:04 -0700 Subject: [PATCH 078/181] simulate purchases --- unit/api/authorization_request_resource.py | 8 ++ unit/api/authorization_resource.py | 2 +- unit/api/transaction_resource.py | 17 +++ unit/models/authorization_request.py | 37 ++++++ unit/models/transaction.py | 131 +++++++++++++++++++-- 5 files changed, 187 insertions(+), 8 deletions(-) diff --git a/unit/api/authorization_request_resource.py b/unit/api/authorization_request_resource.py index 56815ee8..e61f538e 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 sb_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/transaction_resource.py b/unit/api/transaction_resource.py index abcb8e38..126e304f 100644 --- a/unit/api/transaction_resource.py +++ b/unit/api/transaction_resource.py @@ -39,3 +39,20 @@ def update(self, request: PatchTransactionRequest) -> Union[UnitResponse[Transac else: return UnitError.from_json_api(response.json()) + def sb_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 sb_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()) \ No newline at end of file diff --git a/unit/models/authorization_request.py b/unit/models/authorization_request.py index f654701d..80a521cd 100644 --- a/unit/models/authorization_request.py +++ b/unit/models/authorization_request.py @@ -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/transaction.py b/unit/models/transaction.py index 58aec0fa..223525d1 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -131,14 +131,16 @@ def from_json_api(_id, _type, attributes, 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], + summary: str, last_4_digits: str, merchantName: str, merchantType:str, merchantLocation: str, + 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]]): 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 + self.attributes["last4Digits"] = last_4_digits + self.attributes["merchantName"] = merchantName + self.attributes["merchantType"] = merchantType + self.attributes["merchantLocation"] = merchantLocation self.attributes["coordinates"] = coordinates self.attributes["recurring"] = recurring self.attributes["interchange"] = interchange @@ -153,8 +155,9 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b def from_json_api(_id, _type, attributes, relationships): 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.get("coordinates")), + attributes["amount"], attributes["balance"], attributes.get("summary"), attributes["last4Digits"], + attributes.get("merchantName"), attributes.get("merchantType"), attributes.get("merchantLocation"), + Coordinates.from_json_api(attributes.get("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"), @@ -463,4 +466,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": "The Home Depot", + "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 From 28622343c77d2b6af0769ae3df016a6da35daf9a Mon Sep 17 00:00:00 2001 From: Eric Ghildyal Date: Thu, 3 Aug 2023 11:24:52 -0400 Subject: [PATCH 079/181] Update objects with unit dto --- unit/models/__init__.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index dfcf1f47..38ae37c8 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -165,7 +165,7 @@ def __str__(self): EntityType = Literal["Corporation", "LLC", "Partnership"] -class FullName(object): +class FullName(UnitDTO): def __init__(self, first: str, last: str): self.first = first self.last = last @@ -179,7 +179,7 @@ def from_json_api(data: Dict): # todo: Alex - use typing.Literal for multi accepted values (e.g country) -class Address(object): +class Address(UnitDTO): def __init__( self, street: str, @@ -208,7 +208,7 @@ def from_json_api(data: Dict): ) -class Phone(object): +class Phone(UnitDTO): def __init__(self, country_code: str, number: str): self.country_code = country_code self.number = number @@ -218,7 +218,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 @@ -233,7 +233,7 @@ def from_json_api(data: Dict): ) -class Officer(object): +class Officer(UnitDTO): def __init__( self, full_name: FullName, @@ -274,7 +274,7 @@ def from_json_api(data: Dict): ) -class BeneficialOwner(object): +class BeneficialOwner(UnitDTO): def __init__( self, full_name: FullName, @@ -320,7 +320,7 @@ def from_json_api(l: List): return beneficial_owners -class AuthorizedUser(object): +class AuthorizedUser(UnitDTO): def __init__(self, full_name: FullName, email: str, phone: Phone): self.full_name = full_name self.email = email @@ -338,7 +338,7 @@ def from_json_api(l: List) -> List: return authorized_users -class WireCounterparty(object): +class WireCounterparty(UnitDTO): def __init__( self, routing_number: str, account_number: str, name: str, address: Address ): @@ -357,7 +357,7 @@ def from_json_api(data: Dict): ) -class Counterparty(object): +class Counterparty(UnitDTO): def __init__( self, routing_number: str, account_number: str, account_type: str, name: str ): @@ -376,7 +376,7 @@ def from_json_api(data: Dict): ) -class Coordinates(object): +class Coordinates(UnitDTO): def __init__(self, longitude: int, latitude: int): self.longitude = longitude self.latitude = latitude @@ -389,7 +389,7 @@ def from_json_api(data: Dict): return None -class Merchant(object): +class Merchant(UnitDTO): def __init__( self, name: str, type: int, category: Optional[str], location: Optional[str] ): @@ -405,7 +405,7 @@ def from_json_api(data: Dict): ) -class CardLevelLimits(object): +class CardLevelLimits(UnitDTO): def __init__( self, daily_withdrawal: int, @@ -428,7 +428,7 @@ def from_json_api(data: Dict): ) -class CardTotals(object): +class CardTotals(UnitDTO): def __init__(self, withdrawals: int, deposits: int, purchases: int): self.withdrawals = withdrawals self.deposits = deposits @@ -439,7 +439,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 From 88ad935c3ac19242cfa040a5eaa738818ed9f45c Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 4 Aug 2023 16:50:11 -0700 Subject: [PATCH 080/181] Add same day flag for ACH payments --- unit/models/payment.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 502fb3f0..228198d2 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -149,9 +149,10 @@ def from_json_api(_id, _type, attributes, 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"): + same_day: Optional[bool] = False, type: str = "achPayment"): self.type = type self.amount = amount + self.same_day = same_day self.description = description self.direction = direction self.idempotency_key = idempotency_key @@ -173,6 +174,9 @@ def to_json_api(self) -> Dict: 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 @@ -185,8 +189,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) + same_day: Optional[bool] = False, direction: str = "Credit"): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day) self.counterparty = counterparty self.addenda = addenda @@ -203,8 +207,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) + same_day: Optional[bool] = False, tags: Optional[Dict[str, str]], direction: str = "Credit"): + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day) self.addenda = addenda self.verify_counterparty_balance = verify_counterparty_balance From fe3576c9980bed0fa381729f41238f5c593f7cb9 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 4 Aug 2023 17:36:46 -0700 Subject: [PATCH 081/181] Fix ordering of function parameters --- unit/models/payment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 228198d2..3f87afd6 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -189,7 +189,7 @@ 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]], - same_day: Optional[bool] = False, direction: str = "Credit"): + direction: str = "Credit", same_day: Optional[bool] = False): CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day) self.counterparty = counterparty self.addenda = addenda @@ -207,7 +207,7 @@ 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], - same_day: Optional[bool] = False, tags: Optional[Dict[str, str]], direction: str = "Credit"): + tags: Optional[Dict[str, str]], same_day: Optional[bool] = False, direction: str = "Credit"): CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day) self.addenda = addenda self.verify_counterparty_balance = verify_counterparty_balance From c38cefa6c05e55149fadf03cf03256c254b8ad8d Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 8 Aug 2023 14:16:40 -0700 Subject: [PATCH 082/181] Fix book payments --- unit/models/payment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 3f87afd6..17507b1b 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -149,7 +149,7 @@ def from_json_api(_id, _type, attributes, 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", - same_day: Optional[bool] = False, type: str = "achPayment"): + type: str = "achPayment", same_day: Optional[bool] = False): self.type = type self.amount = amount self.same_day = same_day @@ -207,7 +207,7 @@ 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]], same_day: Optional[bool] = False, direction: str = "Credit"): + 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) self.addenda = addenda self.verify_counterparty_balance = verify_counterparty_balance From b0be3ffbf2278625e796906510f935798e259fae Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 8 Aug 2023 17:15:33 -0700 Subject: [PATCH 083/181] Use keyword arg for same day to avoid errors --- unit/models/payment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 17507b1b..e8a62392 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -190,7 +190,7 @@ 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", same_day: Optional[bool] = False): - CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day) + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day=same_day) self.counterparty = counterparty self.addenda = addenda @@ -208,7 +208,7 @@ 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", same_day: Optional[bool] = False): - CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day) + CreatePaymentBaseRequest.__init__(self, amount, description, relationships, idempotency_key, tags, direction, same_day=same_day) self.addenda = addenda self.verify_counterparty_balance = verify_counterparty_balance From 8c7219ea93a1a65cb43b684d331098960da158c3 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 16 Aug 2023 14:11:18 -0700 Subject: [PATCH 084/181] replace sb_ with sandbox_ prefix --- unit/api/authorization_request_resource.py | 2 +- unit/api/transaction_resource.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/unit/api/authorization_request_resource.py b/unit/api/authorization_request_resource.py index e61f538e..dc8dca7b 100644 --- a/unit/api/authorization_request_resource.py +++ b/unit/api/authorization_request_resource.py @@ -44,7 +44,7 @@ def decline(self, request: DeclineAuthorizationRequest) -> Union[UnitResponse[Pu else: return UnitError.from_json_api(response.json()) - def sb_simulate(self, request: SimulateAuthorizationRequest) -> Union[UnitResponse[PurchaseAuthorizationRequestDTO], UnitError]: + 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): diff --git a/unit/api/transaction_resource.py b/unit/api/transaction_resource.py index 126e304f..d9291033 100644 --- a/unit/api/transaction_resource.py +++ b/unit/api/transaction_resource.py @@ -39,7 +39,7 @@ def update(self, request: PatchTransactionRequest) -> Union[UnitResponse[Transac else: return UnitError.from_json_api(response.json()) - def sb_simulate_purchase_transaction(self, request: SimulatePurchaseTransaction) -> Union[UnitResponse[PurchaseTransactionDTO], UnitError]: + 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): @@ -48,11 +48,11 @@ def sb_simulate_purchase_transaction(self, request: SimulatePurchaseTransaction) else: return UnitError.from_json_api(response.json()) - def sb_simulate_card_transaction(self, request: SimulateCardTransaction) -> Union[UnitResponse[CardTransactionDTO], UnitError]: + 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()) \ No newline at end of file + return UnitError.from_json_api(response.json()) From 330d08caa75def04a516e59943a1fab88a97688d Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 16 Aug 2023 14:14:45 -0700 Subject: [PATCH 085/181] use merchant name --- unit/models/transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 223525d1..10f15476 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -500,7 +500,7 @@ def to_json_api(self) -> Dict: "amount": self.amount, "direction": self.direction, "last4Digits": self.last_4_Digits, - "merchantName": "The Home Depot", + "merchantName": self.merchantName, "merchantType": self.merchantType, "merchantLocation": self.merchantLocation, "recurring": False From c13b1e79a89e6682117b79198347f9e9b0014343 Mon Sep 17 00:00:00 2001 From: Julia Park Date: Thu, 17 Aug 2023 12:17:55 -0400 Subject: [PATCH 086/181] Fix typo --- unit/models/fee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d25d2dcc0e7788a2f34cb97f1025c151a0d5ee96 Mon Sep 17 00:00:00 2001 From: Julia Park Date: Thu, 17 Aug 2023 15:38:13 -0400 Subject: [PATCH 087/181] Add batch releases to Unit object --- unit/__init__.py | 2 ++ unit/api/batch_release_resource.py | 17 +++++++++ unit/models/batch_release.py | 57 ++++++++++++++++++++++++++++++ unit/models/codecs.py | 4 +++ 4 files changed, 80 insertions(+) create mode 100644 unit/api/batch_release_resource.py create mode 100644 unit/models/batch_release.py diff --git a/unit/__init__.py b/unit/__init__.py index bc74f256..c5902c7b 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -1,4 +1,5 @@ from unit.api.application_resource import ApplicationResource +from unit.api.batch_release_resource import BatchReleaseResource from unit.api.customer_resource import CustomerResource from unit.api.account_resource import AccountResource from unit.api.card_resource import CardResource @@ -52,3 +53,4 @@ def __init__(self, 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) diff --git a/unit/api/batch_release_resource.py b/unit/api/batch_release_resource.py new file mode 100644 index 00000000..83f8482a --- /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[CreateBatchReleaseRequest]) -> 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[BatchReleaseDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) # is this right based on list?? + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/models/batch_release.py b/unit/models/batch_release.py new file mode 100644 index 00000000..8d896e7f --- /dev/null +++ b/unit/models/batch_release.py @@ -0,0 +1,57 @@ +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"], attributes["senderAddress"], attributes["senderAccountNumber"], attributes.get("tags"), relationships) + + +class CreateBatchReleaseRequest(object): + 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["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + + if self.tags: + payload["data"]["attributes"]["tags"] = self.tags + + return payload diff --git a/unit/models/codecs.py b/unit/models/codecs.py index ed7d296e..d8fd1bb5 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -2,6 +2,7 @@ from unit.models import * from datetime import datetime, date +from unit.models.batch_release import BatchReleaseDTO from unit.models.reward import RewardDTO from unit.utils import date_utils from unit.models.applicationForm import ApplicationFormDTO @@ -275,6 +276,9 @@ "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), + } From 47333ce1e4182958aa63af8afdbe7ab926d5a021 Mon Sep 17 00:00:00 2001 From: Julia Park Date: Thu, 17 Aug 2023 15:41:17 -0400 Subject: [PATCH 088/181] rename --- unit/api/batch_release_resource.py | 2 +- unit/models/batch_release.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/api/batch_release_resource.py b/unit/api/batch_release_resource.py index 83f8482a..28f5d00b 100644 --- a/unit/api/batch_release_resource.py +++ b/unit/api/batch_release_resource.py @@ -8,7 +8,7 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = 'batch-releases' - def create(self, batch_releases: List[CreateBatchReleaseRequest]) -> Union[UnitResponse[List[BatchReleaseDTO]], UnitError]: + def create(self, batch_releases: List[CreateBatchRelease]) -> 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") diff --git a/unit/models/batch_release.py b/unit/models/batch_release.py index 8d896e7f..1b0264e9 100644 --- a/unit/models/batch_release.py +++ b/unit/models/batch_release.py @@ -22,7 +22,7 @@ def from_json_api(_id, _type, attributes, relationships): return BatchReleaseDTO(_id, attributes["amount"], attributes["description"], attributes["senderName"], attributes["senderAddress"], attributes["senderAccountNumber"], attributes.get("tags"), relationships) -class CreateBatchReleaseRequest(object): +class CreateBatchRelease(object): 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): From 658772b428fada56470f200ba59e0b4527ba7c7d Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 17 Aug 2023 15:13:39 -0700 Subject: [PATCH 089/181] Fix purchaseTransactionDTO cardLast4Digits key error --- unit/models/transaction.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 10f15476..e89438d2 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -131,13 +131,13 @@ def from_json_api(_id, _type, attributes, relationships): class PurchaseTransactionDTO(BaseTransactionDTO): def __init__(self, id: str, created_at: datetime, direction: str, amount: int, balance: int, - summary: str, last_4_digits: str, merchantName: str, merchantType:str, merchantLocation: str, + summary: str, card_last_4_digits: str, merchantName: str, merchantType:str, merchantLocation: str, 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]]): BaseTransactionDTO.__init__(self, id, created_at, direction, amount, balance, summary, tags, relationships) self.type = 'purchaseTransaction' - self.attributes["last4Digits"] = last_4_digits + self.attributes["cardLast4Digits"] = card_last_4_digits self.attributes["merchantName"] = merchantName self.attributes["merchantType"] = merchantType self.attributes["merchantLocation"] = merchantLocation @@ -155,7 +155,7 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b def from_json_api(_id, _type, attributes, relationships): return PurchaseTransactionDTO( _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes.get("summary"), attributes["last4Digits"], + attributes["amount"], attributes["balance"], attributes.get("summary"), attributes["cardLast4Digits"], attributes.get("merchantName"), attributes.get("merchantType"), attributes.get("merchantLocation"), Coordinates.from_json_api(attributes.get("coordinates")), attributes["recurring"], attributes.get("interchange"), attributes.get("ecommerce"), @@ -499,7 +499,7 @@ def to_json_api(self) -> Dict: "attributes": { "amount": self.amount, "direction": self.direction, - "last4Digits": self.last_4_Digits, + "cardLast4Digits": self.card_last_4_Digits, "merchantName": self.merchantName, "merchantType": self.merchantType, "merchantLocation": self.merchantLocation, From dbe0074d8073071f0d50f4852ef736aca3a4dd6b Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 17 Aug 2023 16:05:43 -0700 Subject: [PATCH 090/181] Updates DTOs to accomodate purchase simulation responses --- unit/models/transaction.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index e89438d2..ae1c3458 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -134,7 +134,7 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b summary: str, card_last_4_digits: str, merchantName: str, merchantType:str, merchantLocation: str, 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]]): + tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]], 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 @@ -151,17 +151,21 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b self.attributes["cardVerificationData"] = card_verification_data self.attributes["cardNetwork"] = card_network + # 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): return PurchaseTransactionDTO( _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes.get("summary"), attributes["cardLast4Digits"], + attributes["amount"], attributes["balance"], attributes.get("summary"), attributes.get("cardLast4Digits", None), attributes.get("merchantName"), attributes.get("merchantType"), attributes.get("merchantLocation"), Coordinates.from_json_api(attributes.get("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) + relationships, attributes.get("last4Digits", None)) class AtmTransactionDTO(BaseTransactionDTO): @@ -499,7 +503,7 @@ def to_json_api(self) -> Dict: "attributes": { "amount": self.amount, "direction": self.direction, - "cardLast4Digits": self.card_last_4_Digits, + "last4Digits": self.last_4_Digits, "merchantName": self.merchantName, "merchantType": self.merchantType, "merchantLocation": self.merchantLocation, From 5ce53918549826aaeb6cefe27cc64b08e7ef5cd5 Mon Sep 17 00:00:00 2001 From: Julia Park Date: Mon, 21 Aug 2023 09:19:27 -0400 Subject: [PATCH 091/181] fix error --- unit/models/batch_release.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unit/models/batch_release.py b/unit/models/batch_release.py index 1b0264e9..7374e2d2 100644 --- a/unit/models/batch_release.py +++ b/unit/models/batch_release.py @@ -30,7 +30,7 @@ def __init__(self, amount: int, description: str, sender_name: str, sender_addre self.description = description self.sender_name = sender_name self.sender_address = sender_address - self.sender_account_number = sender_account_number + self.sender_account_number = sender_account_number # BIN followed by last four digits? self.tags = tags self.idempotency_key = idempotency_key self.relationships = relationships @@ -49,9 +49,9 @@ def to_json_api(self) -> Dict: } if self.idempotency_key: - payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key + payload["attributes"]["idempotencyKey"] = self.idempotency_key if self.tags: - payload["data"]["attributes"]["tags"] = self.tags + payload["attributes"]["tags"] = self.tags return payload From 0a1ce8aa5e4f9201031afd609e5935ff7a61f966 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 22 Aug 2023 16:20:14 -0700 Subject: [PATCH 092/181] Adds CheckDepositPendingReviewEvent and CheckDepositPendingEvent events --- unit/models/event.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/unit/models/event.py b/unit/models/event.py index e6fcfa72..9e16a02f 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -242,6 +242,30 @@ 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, 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["status"] = status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes.get("tags"), relationships) + +class CheckDepositPendingEvent(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 = 'checkDeposit.pending' + self.attributes["status"] = status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], 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]]): From b734e22546e42073bdf3e0100843032268758fb0 Mon Sep 17 00:00:00 2001 From: Julia Park Date: Wed, 23 Aug 2023 13:47:01 -0400 Subject: [PATCH 093/181] fix issues --- unit/api/batch_release_resource.py | 4 ++-- unit/models/batch_release.py | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/unit/api/batch_release_resource.py b/unit/api/batch_release_resource.py index 28f5d00b..e8928741 100644 --- a/unit/api/batch_release_resource.py +++ b/unit/api/batch_release_resource.py @@ -8,10 +8,10 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = 'batch-releases' - def create(self, batch_releases: List[CreateBatchRelease]) -> Union[UnitResponse[List[BatchReleaseDTO]], UnitError]: + 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[BatchReleaseDTO](DtoDecoder.decode(data), DtoDecoder.decode(data)) # is this right based on list?? + return UnitResponse[List[BatchReleaseDTO]](DtoDecoder.decode(data), None) else: return UnitError.from_json_api(response.json()) diff --git a/unit/models/batch_release.py b/unit/models/batch_release.py index 7374e2d2..4e65af13 100644 --- a/unit/models/batch_release.py +++ b/unit/models/batch_release.py @@ -19,10 +19,10 @@ def __init__(self, id: str, amount: int, description: str, sender_name: str, sen @staticmethod def from_json_api(_id, _type, attributes, relationships): - return BatchReleaseDTO(_id, attributes["amount"], attributes["description"], attributes["senderName"], attributes["senderAddress"], attributes["senderAccountNumber"], attributes.get("tags"), relationships) + return BatchReleaseDTO(_id, attributes["amount"], attributes["description"], attributes["senderName"], Address.from_json_api(attributes["senderAddress"]), attributes["senderAccountNumber"], attributes.get("tags"), relationships) -class CreateBatchRelease(object): +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): @@ -30,7 +30,7 @@ def __init__(self, amount: int, description: str, sender_name: str, sender_addre self.description = description self.sender_name = sender_name self.sender_address = sender_address - self.sender_account_number = sender_account_number # BIN followed by last four digits? + self.sender_account_number = sender_account_number self.tags = tags self.idempotency_key = idempotency_key self.relationships = relationships @@ -55,3 +55,6 @@ def to_json_api(self) -> Dict: payload["attributes"]["tags"] = self.tags return payload + + def __repr__(self): + return json.dumps(self.to_json_api()) From bed432b8bd7b0fafd53a5209af96b05ce5be88a8 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Mon, 28 Aug 2023 12:51:15 -0700 Subject: [PATCH 094/181] Check deposits --- unit/__init__.py | 2 + unit/api/check_deposit_resource.py | 23 +++++++++++ unit/models/__init__.py | 17 ++++++++ unit/models/check_deposit.py | 63 ++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 unit/api/check_deposit_resource.py create mode 100644 unit/models/check_deposit.py diff --git a/unit/__init__.py b/unit/__init__.py index bc74f256..12626407 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -1,4 +1,5 @@ from unit.api.application_resource import ApplicationResource +from unit.api.check_deposit_resource import CheckDepositResource from unit.api.customer_resource import CustomerResource from unit.api.account_resource import AccountResource from unit.api.card_resource import CardResource @@ -34,6 +35,7 @@ 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) diff --git a/unit/api/check_deposit_resource.py b/unit/api/check_deposit_resource.py new file mode 100644 index 00000000..c5b5bc13 --- /dev/null +++ b/unit/api/check_deposit_resource.py @@ -0,0 +1,23 @@ +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() diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 38ae37c8..1b534e31 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -376,6 +376,23 @@ def from_json_api(data: Dict): ) +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 + + @staticmethod + def from_json_api(data: Dict): + return CheckCounterparty( + data["routingNumber"], + data["accountNumber"], + data["name"], + ) + + class Coordinates(UnitDTO): def __init__(self, longitude: int, latitude: int): self.longitude = longitude 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, + ) From 40aa08fcfa1435ccb2f861844cada765cea7d375 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 29 Aug 2023 09:34:52 -0700 Subject: [PATCH 095/181] Check deposit webhook updates --- unit/models/event.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 9e16a02f..63ee5462 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -242,29 +242,32 @@ 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, status: str, tags: Optional[Dict[str, str]], + 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["status"] = status + self.attributes["previousStatus"] = previous_status @staticmethod def from_json_api(_id, _type, attributes, relationships): return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["status"], attributes.get("tags"), relationships) + attributes["previousStatus"], attributes.get("tags"), relationships) + class CheckDepositPendingEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, status: str, tags: Optional[Dict[str, str]], + 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["status"] = status + self.attributes["previousStatus"] = previous_status @staticmethod def from_json_api(_id, _type, attributes, relationships): return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["status"], attributes.get("tags"), relationships) + attributes["previous_status"], attributes.get("tags"), relationships) + class CheckDepositClearingEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Optional[Dict[str, str]], @@ -292,6 +295,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]]): @@ -305,6 +323,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes["previousStatus"], 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]]): From 6fd187d50f7de401ef14f7f31b6f9cbd2ef7dff1 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 7 Sep 2023 18:45:31 -0700 Subject: [PATCH 096/181] updates --- unit/models/event.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 63ee5462..9a32b7dc 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -483,14 +483,17 @@ 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, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, - AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, - AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, - CheckDepositCreatedEvent, CheckDepositClearingEvent, CheckDepositSentEvent, - CheckDepositReturnedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, - PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject] +EventDTO = Union[ + AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, + ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, + AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, + AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, + AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, + CheckDepositCreatedEvent, CheckDepositPendingReviewEvent, CheckDepositPendingEvent, + CheckDepositClearingEvent, CheckDepositSentEvent, CheckDepositRejectedEvent, CheckDepositReturnedEvent, + CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, + StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, +] class ListEventParams(UnitParams): From 671f7b48dd6f0408031be2b30aa43a686b207d24 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Fri, 8 Sep 2023 09:56:19 -0700 Subject: [PATCH 097/181] Update codecs for Check Deposit Webhooks --- unit/models/codecs.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index d8fd1bb5..8c187234 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -204,12 +204,24 @@ "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), + "payment.clearing": lambda _id, _type, attributes, relationships: PaymentClearingEvent.from_json_api(_id, _type, attributes, relationships), From 284716bafb4ae44094ac7e7c7b3838e3859f8e1c Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Thu, 14 Sep 2023 15:12:30 -0700 Subject: [PATCH 098/181] Fix previous status typo --- unit/models/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 9a32b7dc..97893ff8 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -266,7 +266,7 @@ def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Op @staticmethod def from_json_api(_id, _type, attributes, relationships): return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), - attributes["previous_status"], attributes.get("tags"), relationships) + attributes["previousStatus"], attributes.get("tags"), relationships) class CheckDepositClearingEvent(BaseEvent): From 586cb04192e175355046e7f2344b8a77ab4336b0 Mon Sep 17 00:00:00 2001 From: Julia Park Date: Wed, 20 Sep 2023 16:35:58 -0400 Subject: [PATCH 099/181] Add get_image --- unit/api/check_deposit_resource.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/unit/api/check_deposit_resource.py b/unit/api/check_deposit_resource.py index c5b5bc13..53f8e779 100644 --- a/unit/api/check_deposit_resource.py +++ b/unit/api/check_deposit_resource.py @@ -21,3 +21,11 @@ def get(self, check_deposit_id: str) -> Union[UnitResponse[CheckDepositDTO], Uni 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()) From e24074dac28934200364e721a8a51f80beeeae7e Mon Sep 17 00:00:00 2001 From: Julia Park Date: Mon, 30 Oct 2023 16:26:56 -0400 Subject: [PATCH 100/181] Add account and routing numbers to ListCounterpartyParams --- unit/models/counterparty.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/unit/models/counterparty.py b/unit/models/counterparty.py index e043b414..987c4ba7 100644 --- a/unit/models/counterparty.py +++ b/unit/models/counterparty.py @@ -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 From ff578893636786d92a9bf66a056cd976099c8432 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Mon, 13 Nov 2023 08:03:09 -0800 Subject: [PATCH 101/181] Category optional --- unit/models/authorization_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/authorization_request.py b/unit/models/authorization_request.py index 80a521cd..116047de 100644 --- a/unit/models/authorization_request.py +++ b/unit/models/authorization_request.py @@ -29,7 +29,7 @@ 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) From bcfb02490fbc27b0abb4219439c0074ef96bd970 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:04:21 -0500 Subject: [PATCH 102/181] Update purchase and merchant DTOs (#20) * Update purchase and merchant DTOs * Add default values for optional fields --- unit/models/__init__.py | 93 +++++++++++++++++++++++++++++++++++++- unit/models/transaction.py | 28 +++++++----- 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 1b534e31..face4c16 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -408,17 +408,18 @@ def from_json_api(data: Dict): class Merchant(UnitDTO): def __init__( - self, name: str, type: int, category: Optional[str], location: Optional[str] + 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.get("category"), data.get("location") + data["name"], data["type"], data.get("category"), data.get("location"), data.get("id") ) @@ -470,3 +471,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/transaction.py b/unit/models/transaction.py index ae1c3458..3cbc939f 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -131,16 +131,17 @@ def from_json_api(_id, _type, attributes, 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, merchantName: str, merchantType:str, merchantLocation: str, - 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]], last_4_digits: str = None): + summary: str, card_last_4_digits: str, merchant: 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["merchantName"] = merchantName - self.attributes["merchantType"] = merchantType - self.attributes["merchantLocation"] = merchantLocation + self.attributes["merchant"] = merchant self.attributes["coordinates"] = coordinates self.attributes["recurring"] = recurring self.attributes["interchange"] = interchange @@ -150,6 +151,10 @@ 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: @@ -159,13 +164,14 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b def from_json_api(_id, _type, attributes, relationships): return PurchaseTransactionDTO( _id, date_utils.to_datetime(attributes["createdAt"]), attributes["direction"], - attributes["amount"], attributes["balance"], attributes.get("summary"), attributes.get("cardLast4Digits", None), - attributes.get("merchantName"), attributes.get("merchantType"), attributes.get("merchantLocation"), - Coordinates.from_json_api(attributes.get("coordinates")), + attributes["amount"], attributes["balance"], attributes["summary"], attributes["cardLast4Digits"], + Merchant.from_json_api(attributes["merchant"]), Coordinates.from_json_api(attributes.get("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, attributes.get("last4Digits", None)) + relationships, attributes.get("grossInterchange"), attributes.get("cashWithdrawalAmount"), + CurrencyConversion.from_json_api(attributes.get("currencyConversion")), + RichMerchantData.from_json_api(attributes.get("richMerchantData"))) class AtmTransactionDTO(BaseTransactionDTO): From df9cb60913975a0d30844a271558189f3f8ffd56 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:31:27 -0500 Subject: [PATCH 103/181] Fix KeyError when converting from json (#21) * Fix KeyError when convertion from json * Fix argument ordering * Fix argument ordering again --- unit/models/transaction.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 3cbc939f..28d51bce 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -133,10 +133,11 @@ 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: 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, + 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' @@ -163,15 +164,15 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b @staticmethod def from_json_api(_id, _type, attributes, relationships): 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.get("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"), + _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")), 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"))) + RichMerchantData.from_json_api(attributes.get("richMerchantData")), attributes.get("last4Digits", None)) class AtmTransactionDTO(BaseTransactionDTO): From 8dc3d7c6b7c08bdcd016d083313fcf0da925f235 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:51:22 -0500 Subject: [PATCH 104/181] Account for incorrect merchant fields for simulations (#22) --- unit/models/transaction.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 28d51bce..0b1b17c4 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -138,7 +138,7 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b 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): + 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 @@ -163,10 +163,16 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b @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.get("summary"), attributes.get("cardLast4Digits", None), - Merchant.from_json_api(attributes.get("merchant")), Coordinates.from_json_api(attributes.get("coordinates")), + 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"), From f3b7775d881594732c8fce0b8e6ee90105f27a09 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:44:04 -0500 Subject: [PATCH 105/181] Response should output bytes (#23) --- unit/api/statement_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/api/statement_resource.py b/unit/api/statement_resource.py index be711e1f..791acb7f 100644 --- a/unit/api/statement_resource.py +++ b/unit/api/statement_resource.py @@ -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()) From 6120e8dba426ff51f1677dadab30a984acfd5199 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 6 Mar 2024 16:34:28 -0800 Subject: [PATCH 106/181] astra pushToCard payment --- unit/models/payment.py | 39 +++++++++++++++++++++++++--- unit_python_sdk.egg-info/PKG-INFO | 1 + unit_python_sdk.egg-info/SOURCES.txt | 4 +++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index e8a62392..897a20ab 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]): 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], @@ -108,6 +113,21 @@ 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 = 'bookPayment' + + @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["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]]): @@ -149,7 +169,7 @@ def from_json_api(_id, _type, attributes, 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", same_day: Optional[bool] = False): + type: str = "achPayment", same_day: Optional[bool] = False, configuration: dict = None): self.type = type self.amount = amount self.same_day = same_day @@ -158,6 +178,7 @@ def __init__(self, amount: int, description: str, relationships: Dict[str, Relat self.idempotency_key = idempotency_key self.tags = tags self.relationships = relationships + self.configuration = configuration def to_json_api(self) -> Dict: payload = { @@ -181,6 +202,9 @@ def to_json_api(self) -> Dict: if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.configuration: + payload["data"]["attributes"]["configuration"] = self.configuration + return payload def __repr__(self): @@ -326,8 +350,17 @@ 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, + direction: str = "Credit"): + super().__init__(amount, description, relationships, idempotency_key, tags, direction, "pushToCardPayment", False, configuration) + + CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, - CreateBookPaymentRequest, CreateWirePaymentRequest] + CreateBookPaymentRequest, CreateWirePaymentRequest, CreatePushToCardPaymentRequest] class PatchAchPaymentRequest(object): def __init__(self, payment_id: str, tags: Dict[str, str]): diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO index c3be286a..f8e13e2e 100644 --- a/unit_python_sdk.egg-info/PKG-INFO +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -16,3 +16,4 @@ 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 index da93de6f..9f0dc940 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -14,8 +14,10 @@ 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/counterparty_resource.py unit/api/customerToken_resource.py unit/api/customer_resource.py @@ -39,9 +41,11 @@ 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/codecs.py unit/models/counterparty.py unit/models/customer.py From 465e84f68111bf32fb4eaef81f2f3fe251f10aee Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 6 Mar 2024 16:36:41 -0800 Subject: [PATCH 107/181] revert inadvertant changes --- unit_python_sdk.egg-info/PKG-INFO | 1 - unit_python_sdk.egg-info/SOURCES.txt | 4 ---- 2 files changed, 5 deletions(-) diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO index f8e13e2e..c3be286a 100644 --- a/unit_python_sdk.egg-info/PKG-INFO +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -16,4 +16,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 index 9f0dc940..da93de6f 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -14,10 +14,8 @@ 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/counterparty_resource.py unit/api/customerToken_resource.py unit/api/customer_resource.py @@ -41,11 +39,9 @@ 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/codecs.py unit/models/counterparty.py unit/models/customer.py From 5ce31e0c68de7289aaba67f7521ef880efb1956c Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:47:15 -0400 Subject: [PATCH 108/181] Update title literal and individual application (#25) --- unit/models/__init__.py | 3 ++- unit/models/application.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index face4c16..f9edce5d 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -161,7 +161,8 @@ def __str__(self): Status = Literal["Approved", "Denied", "PendingReview"] -Title = Literal["CEO", "COO", "CFO", "President"] +Title = Literal["CEO", "COO", "CFO", "President", "BenefitsAdministrationOfficer", "CIO", "VP", "AVP", "Treasurer", + "Secretary", "Controller", "Manager", "Partner", "Member"] EntityType = Literal["Corporation", "LLC", "Partnership"] diff --git a/unit/models/application.py b/unit/models/application.py index bfb450b7..488b3054 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -262,6 +262,7 @@ def __init__( device_fingerprints: Optional[List[DeviceFingerprint]] = None, idempotency_key: str = None, tags: Optional[Dict[str, str]] = None, + business_vertical: Optional[BusinessVertical] = None, ): self.full_name = full_name self.date_of_birth = date_of_birth @@ -278,6 +279,7 @@ def __init__( self.device_fingerprints = device_fingerprints self.idempotency_key = idempotency_key self.tags = tags + self.business_vertical = business_vertical def to_json_api(self) -> Dict: payload = { @@ -327,6 +329,9 @@ def to_json_api(self) -> Dict: if self.tags: payload["data"]["attributes"]["tags"] = self.tags + if self.business_vertical: + payload["data"]["attributes"]["businessVertical"] = self.business_vertical + return payload def __repr__(self): From 58aa3a28bee70bdf9544064ad1966645cf7c7d59 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 20 Mar 2024 11:14:19 -0700 Subject: [PATCH 109/181] pushToCardPayment on dto --- unit/models/payment.py | 2 +- unit_python_sdk.egg-info/PKG-INFO | 1 + unit_python_sdk.egg-info/SOURCES.txt | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 897a20ab..e9836c83 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -119,7 +119,7 @@ def __init__(self, id: str, created_at: datetime, status: PaymentStatus, directi 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 = 'bookPayment' + self.type = 'pushToCardPayment' @staticmethod def from_json_api(_id, _type, attributes, relationships): diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO index c3be286a..f8e13e2e 100644 --- a/unit_python_sdk.egg-info/PKG-INFO +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -16,3 +16,4 @@ 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 index da93de6f..9f0dc940 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -14,8 +14,10 @@ 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/counterparty_resource.py unit/api/customerToken_resource.py unit/api/customer_resource.py @@ -39,9 +41,11 @@ 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/codecs.py unit/models/counterparty.py unit/models/customer.py From 4e9899b6771ce0b9bf2e1933fdc26ccecd416eeb Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 20 Mar 2024 14:20:27 -0700 Subject: [PATCH 110/181] Fix payment dto --- unit/models/payment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index e9836c83..18a7bbf2 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -9,7 +9,7 @@ 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]], astra_routine_id: Optional[str]): + 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} From eaef877a10f6c7089b8a4c63489cf3d00c9ef280 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:26:15 -0400 Subject: [PATCH 111/181] Update push to card request #26 --- unit/models/payment.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 18a7bbf2..a1afd0d6 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -168,7 +168,7 @@ def from_json_api(_id, _type, attributes, 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", + 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 @@ -186,13 +186,15 @@ 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 @@ -354,9 +356,8 @@ def to_json_api(self) -> Dict: 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, - direction: str = "Credit"): - super().__init__(amount, description, relationships, idempotency_key, tags, direction, "pushToCardPayment", False, configuration) + idempotency_key: Optional[str] = None, tags: Optional[Dict[str, str]] = None): + super().__init__(amount, description, relationships, idempotency_key, tags, None, "pushToCardPayment", False, configuration) CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, From 2a8470e4b35d660e76856c524e63cdd5393549d8 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 26 Mar 2024 13:13:50 -0700 Subject: [PATCH 112/181] Add check payment resources --- unit/api/check_payment_resource.py | 19 ++++ unit/models/check_payment.py | 141 +++++++++++++++++++++++++++++ unit/models/event.py | 13 +++ 3 files changed, 173 insertions(+) create mode 100644 unit/api/check_payment_resource.py create mode 100644 unit/models/check_payment.py diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py new file mode 100644 index 00000000..78945a84 --- /dev/null +++ b/unit/api/check_payment_resource.py @@ -0,0 +1,19 @@ +from unit.api.base_resource import BaseResource +from unit.models.authorization_request import * +from unit.models.check_payment import ApproveCheckPaymentRequest, CheckPaymentDTO +from unit.models.codecs import DtoDecoder + + +class AuthorizationRequestResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "check-payments" + + 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()) diff --git a/unit/models/check_payment.py b/unit/models/check_payment.py new file mode 100644 index 00000000..e4d85bbc --- /dev/null +++ b/unit/models/check_payment.py @@ -0,0 +1,141 @@ +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[ + "NotSufficientFunds", + "UncollectedFundsHold", + "StopPayment", + "ClosedAccount", + "UnableToLocateAccount", + "FrozenOrBlockedAccount", + "StaleDated", + "PostDated", + "NotValidCheckOrCashItem", + "AlteredOrFictitious", + "UnableToProcess", + "ItemExceedsDollarLimit", + "NotAuthorized", + "ReferToMaker", + "UnusableImage", + "DuplicatePresentment", + "WarrantyBreach", + "UnauthorizedWarrantyBreach" +] +CheckPaymentDeliveryStatus = Literal["Required", "NotRequired", "Approved"] + + +class CheckPaymentDTO(object): + def __init__(self, id: str, created_at: datetime, updated_at: datetime, amount: int, status: CheckPaymentStatus, + description: str, check_number: str, originated: bool, on_us: Optional[str], + on_us_auxiliary: Optional[str], counterparty_routing_number: Optional[str], + return_status_reason: Optional[str], 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["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()) diff --git a/unit/models/event.py b/unit/models/event.py index 97893ff8..158d3218 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -323,6 +323,19 @@ def from_json_api(_id, _type, attributes, relationships): attributes["previousStatus"], 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 CustomerCreatedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, tags: Optional[Dict[str, str]], From 2dbfd38102823d6a8282b01033dfe05ad9700a32 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 26 Mar 2024 13:24:49 -0700 Subject: [PATCH 113/181] Rename --- unit/api/check_payment_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py index 78945a84..1f7905a7 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -4,7 +4,7 @@ from unit.models.codecs import DtoDecoder -class AuthorizationRequestResource(BaseResource): +class CHeckPaymentResource(BaseResource): def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "check-payments" From 5e752e12be84690c79378e3155a338734d59ee83 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 26 Mar 2024 13:25:19 -0700 Subject: [PATCH 114/181] Rename2 --- unit/api/check_payment_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py index 1f7905a7..e5841326 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -4,7 +4,7 @@ from unit.models.codecs import DtoDecoder -class CHeckPaymentResource(BaseResource): +class CheckPaymentResource(BaseResource): def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "check-payments" From b12c9805d1392875393033114d297127602aa802 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 26 Mar 2024 13:29:58 -0700 Subject: [PATCH 115/181] Add check payments to client --- unit/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unit/__init__.py b/unit/__init__.py index 0144d264..27fa8aeb 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -1,6 +1,7 @@ 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.customer_resource import CustomerResource from unit.api.account_resource import AccountResource from unit.api.card_resource import CardResource @@ -56,3 +57,4 @@ def __init__(self, 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) From 915563f6bc576bf6528dff00baeeb43a32c54494 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 26 Mar 2024 15:58:03 -0700 Subject: [PATCH 116/181] Fix enums and add get endpoint for check payments --- unit/api/check_payment_resource.py | 8 ++++++++ unit/models/check_payment.py | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py index e5841326..a838d750 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -9,6 +9,14 @@ 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) diff --git a/unit/models/check_payment.py b/unit/models/check_payment.py index e4d85bbc..77c9ceea 100644 --- a/unit/models/check_payment.py +++ b/unit/models/check_payment.py @@ -6,7 +6,8 @@ CheckPaymentStatus = Literal["New", "Pending", "PendingCancellation", "Canceled", "InDelivery", "Delivered", "ReturnedToSender", "Processed", "PendingReview", "MarkedForReturn", "Returned", "Rejected"] -CheckPaymentAdditionalVerificationStatus = Literal[ +CheckPaymentAdditionalVerificationStatus = Literal["Required", "NotRequired", "Approved"] +CheckPaymentReturnStatusReason = Literal[ "NotSufficientFunds", "UncollectedFundsHold", "StopPayment", @@ -26,14 +27,14 @@ "WarrantyBreach", "UnauthorizedWarrantyBreach" ] -CheckPaymentDeliveryStatus = Literal["Required", "NotRequired", "Approved"] +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, check_number: str, originated: bool, on_us: Optional[str], on_us_auxiliary: Optional[str], counterparty_routing_number: Optional[str], - return_status_reason: Optional[str], reject_reason: 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], From b716cc200743be46bef531d046cc5a2760faf16b Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Sun, 7 Apr 2024 17:18:58 -0700 Subject: [PATCH 117/181] Add check payment transactions --- unit/models/transaction.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 0b1b17c4..1d824ea3 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -383,6 +383,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]]): From ac8bc05426864c290b3e6220a480d23f9f6260ec Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Sun, 7 Apr 2024 17:28:14 -0700 Subject: [PATCH 118/181] Add check payments to other references --- unit/models/codecs.py | 13 +++++++++++++ unit/models/transaction.py | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 8c187234..a9705db4 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -3,6 +3,7 @@ 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 @@ -114,6 +115,12 @@ "returnedCheckDepositTransaction": lambda _id, _type, attributes, relationships: ReturnedCheckDepositTransactionDTO.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), @@ -135,6 +142,9 @@ "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), + "accountStatementDTO": lambda _id, _type, attributes, relationships: StatementDTO.from_json_api(_id, _type, attributes, relationships), @@ -222,6 +232,9 @@ "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), diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 1d824ea3..fab99333 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -443,7 +443,8 @@ def from_json_api(_id, _type, attributes, relationships): PurchaseTransactionDTO, AtmTransactionDTO, FeeTransactionDTO, CardTransactionDTO, CardReversalTransactionDTO, WireTransactionDTO, ReleaseTransactionDTO, AdjustmentTransactionDTO, InterestTransactionDTO, DisputeTransactionDTO, CheckDepositTransactionDTO, - ReturnedCheckDepositTransactionDTO, PaymentAdvanceTransactionDTO, + ReturnedCheckDepositTransactionDTO, CheckPaymentTransactionDTO, + ReturnedCheckPaymentTransactionDTO, PaymentAdvanceTransactionDTO, RepaidPaymentAdvanceTransactionDTO] From 8b9a8c0854166ad2723c5669ecb66ddb7909526b Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Wed, 10 Apr 2024 15:52:35 -0700 Subject: [PATCH 119/181] Add create check payment --- unit/api/check_payment_resource.py | 10 ++++++++++ unit/models/__init__.py | 17 ++++++++++++++++ unit/models/payment.py | 31 +++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py index a838d750..b7e353da 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -2,6 +2,7 @@ from unit.models.authorization_request import * from unit.models.check_payment import ApproveCheckPaymentRequest, CheckPaymentDTO from unit.models.codecs import DtoDecoder +from unit.models.payment import CreateCheckPaymentRequest class CheckPaymentResource(BaseResource): @@ -25,3 +26,12 @@ def approve(self, request: ApproveCheckPaymentRequest) -> Union[UnitResponse[Che return UnitResponse[CheckPaymentDTO](DtoDecoder.decode(data), None) else: return UnitError.from_json_api(response.json()) + + def create(self, request: CreateCheckPaymentRequest) -> Union[UnitResponse[CheckPaymentDTO], 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[CheckPaymentDTO](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 f9edce5d..d2bd085b 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -394,6 +394,23 @@ def from_json_api(data: Dict): ) +class CheckPaymentCounterparty(UnitDTO): + def __init__( + self, name: str, counterparty_moved: bool, address: Address + ): + self.name = name + self.counterparty_moved = counterparty_moved + self.address = address + + @staticmethod + def from_json_api(data: Dict): + return CheckPaymentCounterparty( + data["name"], + data["counterpartyMoved"], + data["address"], + ) + + class Coordinates(UnitDTO): def __init__(self, longitude: int, latitude: int): self.longitude = longitude diff --git a/unit/models/payment.py b/unit/models/payment.py index a1afd0d6..b21cc4cd 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -360,8 +360,37 @@ def __init__(self, amount: int, description: str, configuration: dict, super().__init__(amount, description, relationships, idempotency_key, tags, None, "pushToCardPayment", False, configuration) +class CreateCheckPaymentRequest(UnitRequest): + def __init__( + self, + description: str, + amount: int, + send_date: str, + counterparty: CheckPaymentCounterparty, + idempotency_key: str, + relationships: Dict[str, Relationship], + tags: Optional[Dict[str, str]] = None, + ): + self.description = description + self.amount = amount + self.send_date = send_date + self.counterparty = counterparty + self.description = description + self.idempotency_key = idempotency_key + self.tags = tags + self.relationships = relationships + + def to_json_api(self) -> Dict: + payload = super().to_payload("checkPayment", self.relationships) + payload["data"]["attributes"]["counterparty"]["name"] = self.counterparty.name + payload["data"]["attributes"]["counterparty"]["counterpartyMoved"] = self.counterparty.counterparty_moved + payload["data"]["attributes"]["counterparty"]["address"] = self.counterparty.address + return payload + + CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, - CreateBookPaymentRequest, CreateWirePaymentRequest, CreatePushToCardPaymentRequest] + CreateBookPaymentRequest, CreateWirePaymentRequest, CreatePushToCardPaymentRequest, + CreateCheckPaymentRequest] class PatchAchPaymentRequest(object): def __init__(self, payment_id: str, tags: Dict[str, str]): From 276b244151d82322b9576b73fa346669950cf10b Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Wed, 10 Apr 2024 16:03:47 -0700 Subject: [PATCH 120/181] Include memo --- unit/models/payment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index b21cc4cd..9722ed34 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -365,17 +365,18 @@ def __init__( self, description: str, amount: int, - send_date: str, 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.description = description + self.memo = memo self.idempotency_key = idempotency_key self.tags = tags self.relationships = relationships From 0da1b2c3344399ccee66e5cf445dcd27219f91b2 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Thu, 11 Apr 2024 16:47:12 -0700 Subject: [PATCH 121/181] Remove item assignment --- unit/models/payment.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 9722ed34..882ee828 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -382,11 +382,7 @@ def __init__( self.relationships = relationships def to_json_api(self) -> Dict: - payload = super().to_payload("checkPayment", self.relationships) - payload["data"]["attributes"]["counterparty"]["name"] = self.counterparty.name - payload["data"]["attributes"]["counterparty"]["counterpartyMoved"] = self.counterparty.counterparty_moved - payload["data"]["attributes"]["counterparty"]["address"] = self.counterparty.address - return payload + return super().to_payload("checkPayment", self.relationships) CreatePaymentRequest = Union[CreateInlinePaymentRequest, CreateLinkedPaymentRequest, CreateVerifiedPaymentRequest, From 815e018e4143c6cf542c63c78f0c681a7e50f7a6 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 12 Apr 2024 11:59:53 -0700 Subject: [PATCH 122/181] Add check payment counterparty to json encoder --- unit/models/codecs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index a9705db4..ea472528 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -429,4 +429,10 @@ 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, + "counterpartyMoved": obj.counterparty_moved, + "address": obj.address + } return json.JSONEncoder.default(self, obj) From 04fda054ec2723f6b2b50da8a6091d3b0d2ec0a6 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 12 Apr 2024 15:40:18 -0700 Subject: [PATCH 123/181] Remove counterpartyMoved param --- unit/models/__init__.py | 4 +--- unit/models/codecs.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index d2bd085b..b8182af3 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -396,17 +396,15 @@ def from_json_api(data: Dict): class CheckPaymentCounterparty(UnitDTO): def __init__( - self, name: str, counterparty_moved: bool, address: Address + self, name: str, address: Address ): self.name = name - self.counterparty_moved = counterparty_moved self.address = address @staticmethod def from_json_api(data: Dict): return CheckPaymentCounterparty( data["name"], - data["counterpartyMoved"], data["address"], ) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index ea472528..560ddf4a 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -432,7 +432,6 @@ def default(self, obj): if isinstance(obj, CheckPaymentCounterparty): return { "name": obj.name, - "counterpartyMoved": obj.counterparty_moved, "address": obj.address } return json.JSONEncoder.default(self, obj) From 4cad6938b0e2be5b4c94d462e6c889c1fde8484d Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Thu, 18 Apr 2024 14:45:36 -0400 Subject: [PATCH 124/181] Add events (#27) --- unit/models/event.py | 201 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/unit/models/event.py b/unit/models/event.py index 158d3218..a998069f 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -323,6 +323,188 @@ 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_status: str, + 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["additionalVerificationStatus"] = additional_verification_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["status"], attributes["additionalVerificationStatus"], + 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_status: str, 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["additionalVerificationStatus"] = additional_verification_status + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return CheckPaymentProcessedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + attributes["previousStatus"], attributes["additionalVerificationStatus"], + 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 CheckPaymentDeliveredEvent(_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]]): @@ -337,6 +519,20 @@ def from_json_api(_id, _type, attributes, relationships): 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]]): @@ -504,6 +700,11 @@ def from_json_api(_id, _type, attributes, relationships): 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, ] From 190adabf4723cfaa7471560b7e9f8a1b8a772bf7 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 23 Apr 2024 11:14:17 -0700 Subject: [PATCH 125/181] Check payment codecs --- unit/models/codecs.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 560ddf4a..9a2c39ef 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -145,6 +145,48 @@ "checkPayment": lambda _id, _type, attributes, relationships: CheckPaymentDTO.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), + "accountStatementDTO": lambda _id, _type, attributes, relationships: StatementDTO.from_json_api(_id, _type, attributes, relationships), From 153149368bcf0afa4a88658fd5e0cb7a9e4a2214 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:32:56 -0400 Subject: [PATCH 126/181] Cancel check payment and check stop payments (#28) * Add cancel for check payment resource * Add stop payments * Add to init * Add related events * Update codecs --- unit/__init__.py | 2 ++ unit/api/check_payment_resource.py | 8 +++++ unit/api/check_stop_payment_resource.py | 20 ++++++++++++ unit/models/check_stop_payment.py | 33 +++++++++++++++++++ unit/models/codecs.py | 9 ++++++ unit/models/event.py | 42 +++++++++++++++++++++++++ unit/models/payment.py | 20 +++++++++++- 7 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 unit/api/check_stop_payment_resource.py create mode 100644 unit/models/check_stop_payment.py diff --git a/unit/__init__.py b/unit/__init__.py index 27fa8aeb..e56f7d54 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -2,6 +2,7 @@ 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 @@ -58,3 +59,4 @@ def __init__(self, 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) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py index b7e353da..96e28c77 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -35,3 +35,11 @@ def create(self, request: CreateCheckPaymentRequest) -> Union[UnitResponse[Check 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()) 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/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 9a2c39ef..4674ea34 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -187,6 +187,15 @@ "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), diff --git a/unit/models/event.py b/unit/models/event.py index a998069f..92a20a6f 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 * @@ -692,6 +694,45 @@ def from_json_api(_id, _type, attributes, relationships): return AccountReopenedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), relationships) +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) + + EventDTO = Union[ AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, @@ -707,6 +748,7 @@ def from_json_api(_id, _type, attributes, relationships): CheckPaymentAdditionalVerificationApprovedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, + StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, ] diff --git a/unit/models/payment.py b/unit/models/payment.py index 882ee828..ba7f5310 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -385,9 +385,27 @@ 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, CreatePushToCardPaymentRequest, - CreateCheckPaymentRequest] + CreateCheckPaymentRequest, CreateCheckStopPaymentRequest] class PatchAchPaymentRequest(object): def __init__(self, payment_id: str, tags: Dict[str, str]): From bf7760d5b1356ba32c5a21f33235affe019492c5 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:34:02 -0400 Subject: [PATCH 127/181] Fix check payment event bugs (#29) * Fix copy error and key errors * Fix another key error --- unit/models/event.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 92a20a6f..ad3cf82b 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -326,17 +326,17 @@ def from_json_api(_id, _type, attributes, relationships): class CheckPaymentCreatedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, status: str, additional_verification_status: str, + def __init__(self, id: str, created_at: datetime, status: str, additional_verification_required: str, 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["additionalVerificationStatus"] = additional_verification_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["additionalVerificationStatus"], + attributes["status"], attributes["additionalVerificationRequired"], attributes.get("tags"), relationships) @@ -471,7 +471,7 @@ def __init__(self, id: str, created_at: datetime, status: str, previous_status: @staticmethod def from_json_api(_id, _type, attributes, relationships): - return CheckPaymentDeliveredEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], + return CheckPaymentReturnToSenderEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], attributes["previousStatus"], attributes.get("tags"), relationships) @@ -495,7 +495,7 @@ def __init__(self, id: str, created_at: datetime, previous_delivery_status: str, 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["newDeliveryStatus"] = new_delivery_status self.attributes["trackedAt"] = tracked_at self.attributes["postalCode"] = postal_code @@ -503,7 +503,7 @@ def __init__(self, id: str, created_at: datetime, previous_delivery_status: str, def from_json_api(_id, _type, attributes, relationships): return CheckPaymentDeliveryStatusChangedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousDeliveryStatus"], - attributes["NewDeliveryStatus"], attributes["trackedAt"], + attributes["newDeliveryStatus"], attributes["trackedAt"], attributes["postalCode"], attributes.get("tags"), relationships) From 0ac8f97fd948c106b296f404cdde2e4a258a0e8c Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:18:42 -0400 Subject: [PATCH 128/181] Fix key error on check payment processed event (#30) --- unit/models/event.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index ad3cf82b..34b85086 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -326,7 +326,7 @@ def from_json_api(_id, _type, attributes, relationships): class CheckPaymentCreatedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, status: str, additional_verification_required: str, + 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' @@ -354,17 +354,17 @@ def from_json_api(_id, _type, attributes, relationships): class CheckPaymentProcessedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, previous_status: str, additional_verification_status: str, tags: Optional[Dict[str, str]], + 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["additionalVerificationStatus"] = additional_verification_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["additionalVerificationStatus"], + attributes["previousStatus"], attributes["additionalVerificationRequired"], attributes.get("tags"), relationships) From 3effb89399906a0740cf7c1a13c2c0101e3e6270 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Thu, 2 May 2024 15:54:19 -0400 Subject: [PATCH 129/181] Make check_number optional (#31) --- unit/models/check_payment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/check_payment.py b/unit/models/check_payment.py index 77c9ceea..28c078ac 100644 --- a/unit/models/check_payment.py +++ b/unit/models/check_payment.py @@ -32,7 +32,7 @@ class CheckPaymentDTO(object): def __init__(self, id: str, created_at: datetime, updated_at: datetime, amount: int, status: CheckPaymentStatus, - description: str, check_number: str, originated: bool, on_us: Optional[str], + 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], @@ -96,7 +96,7 @@ def from_json_api(_id, _type, attributes, relationships): amount=attributes["amount"], status=attributes["status"], description=attributes["description"], - check_number=attributes["checkNumber"], + check_number=attributes.get("checkNumber"), originated=attributes["originated"], on_us=attributes.get("onUs"), on_us_auxiliary=attributes.get("onUsAuxiliary"), From 699e266678ad604973bfa7954ecc449a34f79202 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Wed, 22 May 2024 14:56:44 -0700 Subject: [PATCH 130/181] Add timeout handling to payment create --- unit/api/base_resource.py | 20 ++++++++++---------- unit/api/check_payment_resource.py | 4 ++-- unit/api/payment_resource.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) 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/check_payment_resource.py b/unit/api/check_payment_resource.py index 96e28c77..6e5fd722 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -27,9 +27,9 @@ def approve(self, request: ApproveCheckPaymentRequest) -> Union[UnitResponse[Che else: return UnitError.from_json_api(response.json()) - def create(self, request: CreateCheckPaymentRequest) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: + def create(self, request: CreateCheckPaymentRequest, timeout: float = None) -> Union[UnitResponse[CheckPaymentDTO], UnitError]: payload = request.to_json_api() - response = super().post(f"{self.resource}", payload) + 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) diff --git a/unit/api/payment_resource.py b/unit/api/payment_resource.py index 33c13fda..28a571d5 100644 --- a/unit/api/payment_resource.py +++ b/unit/api/payment_resource.py @@ -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) From edf607221f92ee6e93671b744e4b650fa8bb47fd Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 23 Jul 2024 16:53:15 -0400 Subject: [PATCH 131/181] Update application form version to V2024_06 --- unit/api/applicationForm_resource.py | 8 +++++++- unit_python_sdk.egg-info/SOURCES.txt | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/unit/api/applicationForm_resource.py b/unit/api/applicationForm_resource.py index 3ef83f3e..1a54f1c0 100644 --- a/unit/api/applicationForm_resource.py +++ b/unit/api/applicationForm_resource.py @@ -10,7 +10,13 @@ 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, + headers={ + "X-Accept-Version": "V2024_06" + } + ) if super().is_20x(response.status_code): data = response.json().get("data") return UnitResponse[ApplicationFormDTO](DtoDecoder.decode(data), None) diff --git a/unit_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt index 9f0dc940..9027fe29 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -18,6 +18,8 @@ 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 @@ -46,6 +48,8 @@ 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 From f8cda6f2a4784eb3450c5166d16aeeabd9583806 Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Tue, 23 Jul 2024 16:06:34 -0700 Subject: [PATCH 132/181] Add patch check payment --- unit/models/payment.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index ba7f5310..3682c6c8 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -447,7 +447,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, From 78ecd3b9560d5ba0a874bc9fda6e6cf92c65da00 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 25 Jul 2024 09:56:08 -0400 Subject: [PATCH 133/181] Remove version from application form create resource --- unit/api/applicationForm_resource.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/unit/api/applicationForm_resource.py b/unit/api/applicationForm_resource.py index 1a54f1c0..0aa12c4a 100644 --- a/unit/api/applicationForm_resource.py +++ b/unit/api/applicationForm_resource.py @@ -12,10 +12,7 @@ def create(self, request: CreateApplicationFormRequest) -> Union[UnitResponse[Ap payload = request.to_json_api() response = super().post( self.resource, - payload, - headers={ - "X-Accept-Version": "V2024_06" - } + payload ) if super().is_20x(response.status_code): data = response.json().get("data") From d74ad1586ad411754c76ffbfa344eb525dfb06a7 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 10 Oct 2024 09:18:50 -0400 Subject: [PATCH 134/181] Adds pushToCardPayment codec --- unit/models/codecs.py | 5 ++++- unit/models/payment.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 4674ea34..96f8f17b 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -14,7 +14,7 @@ BusinessVirtualDebitCardDTO, PinStatusDTO, CardLimitsDTO from unit.models.transaction import * from unit.models.payment import AchPaymentDTO, BookPaymentDTO, WirePaymentDTO, AchReceivedPaymentDTO, BillPaymentDTO, \ - SimulateIncomingAchPaymentDTO + SimulateIncomingAchPaymentDTO, PushToCardPaymentDTO from unit.models.customerToken import CustomerTokenDTO, CustomerVerificationTokenDTO from unit.models.fee import FeeDTO from unit.models.event import * @@ -145,6 +145,9 @@ "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), diff --git a/unit/models/payment.py b/unit/models/payment.py index 3682c6c8..4eef3ed7 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -128,6 +128,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes["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]]): From 813df6091d5417c69019937353750461c9e571d4 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 10 Oct 2024 09:50:54 -0400 Subject: [PATCH 135/181] Safe get on astraRoutineId --- unit/models/payment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/payment.py b/unit/models/payment.py index 4eef3ed7..984c69ba 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -125,7 +125,7 @@ def __init__(self, id: str, created_at: datetime, status: PaymentStatus, directi 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["astraRoutineId"], attributes.get("reason"), attributes.get("tags"), + attributes.get("astraRoutineId"), attributes.get("reason"), attributes.get("tags"), relationships) From 2ff164af8c18955e0f22b7cf1b8087ca543f0a25 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Mon, 21 Oct 2024 16:02:09 -0400 Subject: [PATCH 136/181] Update officer dto and application literals (#35) --- unit/models/__init__.py | 53 +++++++++++++++++++------------------- unit/models/application.py | 14 +++++++++- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index b8182af3..7ec43493 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -160,10 +160,23 @@ def __str__(self): ) +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", "BenefitsAdministrationOfficer", "CIO", "VP", "AVP", "Treasurer", "Secretary", "Controller", "Manager", "Partner", "Member"] -EntityType = Literal["Corporation", "LLC", "Partnership"] +EntityType = Literal["Corporation", "LLC", "Partnership", "PubliclyTradedCorporation", "PrivatelyHeldCorporation", + "NotForProfitOrganization"] class FullName(UnitDTO): @@ -235,19 +248,11 @@ def from_json_api(data: Dict): 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, - ): + 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, 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 @@ -258,21 +263,17 @@ def __init__( 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"), - ) + 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"), data.get("idTheftScore"), data.get("occupation"), data.get("annualIncome"), + data.get("sourceOfIncome")) class BeneficialOwner(UnitDTO): diff --git a/unit/models/application.py b/unit/models/application.py index 488b3054..7cbe7b44 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -10,6 +10,18 @@ "AddressVerification", "CertificateOfIncorporation", "EmployerIdentificationNumberConfirmation", + "SocialSecurityCard", + "ClientRequested", + "SelfieVerification", +] + +ApplicationDocumentStatus = Literal[ + "Required", + "ReceivedBack", + "ReceivedFront", + "Invalid", + "Approved", + "PendingReview" ] ReasonCode = Literal[ @@ -435,7 +447,7 @@ class ApplicationDocumentDTO(object): def __init__( self, id: str, - status: ApplicationStatus, + status: ApplicationDocumentStatus, document_type: DocumentType, description: str, name: str, From 64d7728fa963ac839425218b002769aa2ad49fbf Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:53:39 -0400 Subject: [PATCH 137/181] Update application request and beneficial owner (#36) * Update application * Update serialization --- unit/models/__init__.py | 9 ++++++ unit/models/application.py | 61 +++++++++++++++++++++++++++++++++----- unit/models/codecs.py | 14 +++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 7ec43493..aef251eb 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -289,6 +289,9 @@ def __init__( 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 @@ -300,6 +303,9 @@ def __init__( 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): @@ -317,6 +323,9 @@ def from_json_api(l: List): data.get("passport"), data.get("nationality"), data.get("percentage"), + data.get("occupation"), + data.get("annualIncome"), + data.get("sourceOfIncome") ) ) return beneficial_owners diff --git a/unit/models/application.py b/unit/models/application.py index 7cbe7b44..454b9de9 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -203,6 +203,13 @@ def __init__( 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]], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]], ): @@ -216,12 +223,18 @@ def __init__( "status": status, "ssn": ssn, "stateOfIncorporation": state_of_incorporation, - "ssn": ssn, "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, @@ -248,6 +261,13 @@ def from_json_api(_id, _type, attributes, relationships): 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("tags"), relationships, ) @@ -274,6 +294,9 @@ def __init__( 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 @@ -291,7 +314,10 @@ def __init__( 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 = { @@ -341,6 +367,15 @@ def to_json_api(self) -> Dict: 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 @@ -413,13 +448,7 @@ def to_json_api(self) -> dict: "officer": self.officer, "beneficialOwners": self.beneficial_owners, "entityType": self.entity_type, - "industry": self.industry, - "annualRevenue": self.annual_revenue, - "numberOfEmployees": self.number_of_employees, - "cashFlow": self.cash_flow, "yearOfIncorporation": self.year_of_incorporation, - "countriesOfOperation": self.countries_of_operation, - "stockSymbol": self.stock_symbol, "businessVertical": self.business_vertical, }, } @@ -437,6 +466,24 @@ def to_json_api(self) -> dict: 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 + return payload def __repr__(self): diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 96f8f17b..fc83cfae 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -459,6 +459,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), @@ -473,6 +481,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))} From 59304196f16f844493228434a72a0ba54947ee0e Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:17:07 -0500 Subject: [PATCH 138/181] Update customer and account dtos to faciliate archiving/closing (#37) --- unit/api/customer_resource.py | 9 ++++++++ unit/models/account.py | 11 ++++++--- unit/models/customer.py | 43 ++++++++++++++++++++++++++++++----- 3 files changed, 54 insertions(+), 9 deletions(-) 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/models/account.py b/unit/models/account.py index 70dcc444..8d9a41de 100644 --- a/unit/models/account.py +++ b/unit/models/account.py @@ -288,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, } @@ -306,7 +311,7 @@ def to_json_api(self) -> Dict: return payload def __repr__(self): - json.dumps(self.to_json_api()) + return json.dumps(self.to_json_api()) class ListAccountParams(UnitParams): diff --git a/unit/models/customer.py b/unit/models/customer.py index 83ebb29b..d1ceab7b 100644 --- a/unit/models/customer.py +++ b/unit/models/customer.py @@ -1,17 +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], authorized_users: [AuthorizedUser], 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 = '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, "authorizedUsers": authorized_users, "tags": tags} + "nationality": nationality, "authorizedUsers": authorized_users, "tags": tags, + "status": status, "archiveReason": archive_reason} self.relationships = relationships @staticmethod @@ -21,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"), - AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("tags"), relationships + AuthorizedUser.from_json_api(attributes["authorizedUsers"]), attributes.get("tags"), relationships, + attributes.get("status"), attributes.get("archiveReason") ) @@ -29,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 @@ -45,7 +54,8 @@ def from_json_api(_id, _type, attributes, relationships): attributes["stateOfIncorporation"], attributes["ein"], attributes["entityType"], BusinessContact.from_json_api(attributes["contact"]), AuthorizedUser.from_json_api(attributes["authorizedUsers"]), - attributes.get("dba"), attributes.get("tags"), relationships) + attributes.get("dba"), attributes.get("tags"), relationships, attributes.get("status"), + attributes.get("archiveReason")) CustomerDTO = Union[IndividualCustomerDTO, BusinessCustomerDTO] @@ -156,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()) From e7e354333758feebb6b81807dceabdec05d6293b Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Mon, 13 Jan 2025 15:36:07 -0500 Subject: [PATCH 139/181] Implements freeze account endpoint --- unit/api/account_resource.py | 9 +++++++++ unit/models/account.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/unit/api/account_resource.py b/unit/api/account_resource.py index a034469d..a6cad849 100644 --- a/unit/api/account_resource.py +++ b/unit/api/account_resource.py @@ -33,6 +33,15 @@ 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 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): diff --git a/unit/models/account.py b/unit/models/account.py index 8d9a41de..db9054d1 100644 --- a/unit/models/account.py +++ b/unit/models/account.py @@ -314,6 +314,39 @@ def __repr__(self): return json.dumps(self.to_json_api()) + +AccountFreezeReasons = Literal["Other", "Fraud"] + + +class FreezeAccountRequest(UnitRequest): + def __init__( + self, + account_id: str, + reason: AccountFreezeReasons, + reason_text: Optional[str], + ): + self.account_id = account_id + self.reason = reason + self.reason_text = reason_text + self._type = "accountFreeze" + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": self._type, + "attributes": { + "reason": self.reason, + "reason_text": 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[Dict[str, str]] = None, include: Optional[str] = None, From 67f188600759b54c8cadd4ec45dd87ac2f06bb32 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 14 Jan 2025 10:15:06 -0500 Subject: [PATCH 140/181] fix reasonText --- unit/models/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/account.py b/unit/models/account.py index db9054d1..abf62eeb 100644 --- a/unit/models/account.py +++ b/unit/models/account.py @@ -336,7 +336,7 @@ def to_json_api(self) -> Dict: "type": self._type, "attributes": { "reason": self.reason, - "reason_text": self.reason_text, + "reasonText": self.reason_text, } } } From 0694c5f3e04f40b795274f941e8370b673dc3071 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 14 Jan 2025 17:15:47 -0500 Subject: [PATCH 141/181] add type creditAccountFreeze to freeze account request --- unit/models/account.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unit/models/account.py b/unit/models/account.py index abf62eeb..41bf2558 100644 --- a/unit/models/account.py +++ b/unit/models/account.py @@ -316,6 +316,7 @@ def __repr__(self): AccountFreezeReasons = Literal["Other", "Fraud"] +AccountFreezeTypes = Literal["accountFreeze", "creditAccountFreeze"] class FreezeAccountRequest(UnitRequest): @@ -324,11 +325,12 @@ def __init__( 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 = "accountFreeze" + self._type = type def to_json_api(self) -> Dict: payload = { From 4a2500d5d68a2779812ece54a96217a2d211547e Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Tue, 21 Jan 2025 16:24:41 -0500 Subject: [PATCH 142/181] unfreeze account --- unit/api/account_resource.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/unit/api/account_resource.py b/unit/api/account_resource.py index a6cad849..26c35056 100644 --- a/unit/api/account_resource.py +++ b/unit/api/account_resource.py @@ -42,6 +42,14 @@ def freeze_account(self, request: FreezeAccountRequest) -> Union[UnitResponse[Ac 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): From 03d0e691b85599d1b6781b6efac8c142daf94c2f Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:03:00 -0500 Subject: [PATCH 143/181] Add field to individual application dto (#40) Co-authored-by: Julia Park --- unit/models/application.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unit/models/application.py b/unit/models/application.py index 454b9de9..1fd706e5 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -139,6 +139,7 @@ def __init__( ein: Optional[str], dba: Optional[str], sole_proprietorship: Optional[bool], + business_vertical: Optional[BusinessVertical], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]], ): @@ -158,6 +159,7 @@ def __init__( "ein": ein, "dba": dba, "soleProprietorship": sole_proprietorship, + "businessVertical": business_vertical, "tags": tags, } self.relationships = relationships @@ -179,6 +181,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("ein"), attributes.get("dba"), attributes.get("soleProprietorship"), + attributes.get("businessVertical"), attributes.get("tags"), relationships, ) From ea28358813160dffb6adcf6cc8e0e5c42a2c91d4 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:18:04 -0400 Subject: [PATCH 144/181] Add cancel for payments, return_check for check payments (#41) * Add cancel for payments, return_check for check payments * Update request method --------- Co-authored-by: Julia Park --- unit/api/check_payment_resource.py | 11 ++++++++++- unit/api/payment_resource.py | 8 ++++++++ unit/models/check_payment.py | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/unit/api/check_payment_resource.py b/unit/api/check_payment_resource.py index 6e5fd722..cff6a1ea 100644 --- a/unit/api/check_payment_resource.py +++ b/unit/api/check_payment_resource.py @@ -1,6 +1,6 @@ from unit.api.base_resource import BaseResource from unit.models.authorization_request import * -from unit.models.check_payment import ApproveCheckPaymentRequest, CheckPaymentDTO +from unit.models.check_payment import ApproveCheckPaymentRequest, CheckPaymentDTO, ReturnCheckPaymentRequest from unit.models.codecs import DtoDecoder from unit.models.payment import CreateCheckPaymentRequest @@ -43,3 +43,12 @@ def cancel(self, check_payment_id: str) -> Union[UnitResponse[CheckPaymentDTO], 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/payment_resource.py b/unit/api/payment_resource.py index 28a571d5..241ce540 100644 --- a/unit/api/payment_resource.py +++ b/unit/api/payment_resource.py @@ -55,3 +55,11 @@ def simulate_incoming_ach(self, request: SimulateIncomingAchRequest) -> Union[Un 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/models/check_payment.py b/unit/models/check_payment.py index 28c078ac..87a16f87 100644 --- a/unit/models/check_payment.py +++ b/unit/models/check_payment.py @@ -140,3 +140,23 @@ def to_json_api(self) -> Dict: 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()) From e97cbe88652e7618986a57308308c07ff5c9fbcb Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Mon, 12 May 2025 15:57:45 -0400 Subject: [PATCH 145/181] feat(disputes): add dispute created event --- unit/models/event.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 34b85086..4d813037 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -732,6 +732,22 @@ 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, status: str, source: 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) + EventDTO = Union[ AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, @@ -748,7 +764,7 @@ def from_json_api(_id, _type, attributes, relationships): CheckPaymentAdditionalVerificationApprovedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, - StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, + StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, DisputeCreatedEvent, ] From 366b1e34ba9ce15dbdcc5bef6a44831f3817aff8 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Mon, 12 May 2025 18:05:54 -0400 Subject: [PATCH 146/181] feat(disputes): add status changed event --- unit/models/event.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index 4d813037..7573ae67 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -748,6 +748,19 @@ def from_json_api(_id, _type, attributes, relationships): 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) + EventDTO = Union[ AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, @@ -764,7 +777,8 @@ def from_json_api(_id, _type, attributes, relationships): CheckPaymentAdditionalVerificationApprovedEvent, CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, - StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, DisputeCreatedEvent, + StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, + DisputeCreatedEvent, DisputeStatusChangedEvent, ] From 9fd9624be81765fbcea534dd5e2963eb44f36d50 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Tue, 13 May 2025 08:52:46 -0400 Subject: [PATCH 147/181] feat(disputes): add new events to codecs --- unit/models/codecs.py | 6 ++++++ unit/models/event.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index fc83cfae..5682d34c 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -358,6 +358,12 @@ "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), + } diff --git a/unit/models/event.py b/unit/models/event.py index 7573ae67..10e58057 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -733,7 +733,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes["previousStatus"], attributes.get("tags"), relationships) class DisputeCreatedEvent(BaseEvent): - def __init__(self, id: str, created_at: datetime, amount: int, description: str, status: str, source: str, + 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 From 47170f88cd97e902234b58b5d9bb87f95d5a2f94 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Wed, 14 May 2025 11:14:05 -0400 Subject: [PATCH 148/181] feat(disputes): api call to retrieve by id --- unit/__init__.py | 2 ++ unit/api/dispute_resource.py | 18 ++++++++++ unit/models/dispute.py | 54 ++++++++++++++++++++++++++++ unit_python_sdk.egg-info/SOURCES.txt | 2 ++ 4 files changed, 76 insertions(+) create mode 100644 unit/api/dispute_resource.py create mode 100644 unit/models/dispute.py diff --git a/unit/__init__.py b/unit/__init__.py index e56f7d54..7be2e411 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -26,6 +26,7 @@ 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 __all__ = ["api", "models", "utils"] @@ -60,3 +61,4 @@ def __init__(self, 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) diff --git a/unit/api/dispute_resource.py b/unit/api/dispute_resource.py new file mode 100644 index 00000000..209a4571 --- /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_by_id(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/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_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt index 9027fe29..f4f690c1 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -23,6 +23,7 @@ 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 @@ -54,6 +55,7 @@ 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 From 51cd4e647e359d838f695e6717b4b96dbc76883d Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Fri, 16 May 2025 15:32:35 -0400 Subject: [PATCH 149/181] refactor: adapt get dispute endpoint to convention --- unit/api/dispute_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/api/dispute_resource.py b/unit/api/dispute_resource.py index 209a4571..04d7124b 100644 --- a/unit/api/dispute_resource.py +++ b/unit/api/dispute_resource.py @@ -9,7 +9,7 @@ def __init__(self, api_url, token): super().__init__(api_url, token) self.resource = "disputes" - def get_by_id(self, dispute_id: str) -> Union[UnitResponse[DisputeDTO], UnitError]: + 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") From b37794ff939732753b654418defd9dff07b115a6 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Wed, 21 May 2025 17:38:39 -0400 Subject: [PATCH 150/181] feat: add events for account close --- unit/models/codecs.py | 6 ++++++ unit/models/transaction.py | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 5682d34c..b8fbec3a 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -364,6 +364,12 @@ "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), + } diff --git a/unit/models/transaction.py b/unit/models/transaction.py index fab99333..57274c60 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -438,6 +438,42 @@ 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, receiver_counterparty: Dict[str, str], 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' + self.attributes["receiverCounterparty"] = receiver_counterparty + self.attributes["amount"] = amount + self.attributes["direction"] = direction + self.attributes["balance"] = balance + self.attributes["summary"] = summary + self.attributes["tags"] = tags + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AccountLowBalanceClosureTransactionDTO( + _id, date_utils.to_datetime(attributes["createdAt"]), attributes["receiverCounterparty"], + 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' + self.attributes["amount"] = amount + self.attributes["direction"] = direction + self.attributes["balance"] = balance + self.attributes["summary"] = summary + self.attributes["tags"] = tags + + @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) + TransactionDTO = Union[OriginatedAchTransactionDTO, ReceivedAchTransactionDTO, ReturnedAchTransactionDTO, ReturnedReceivedAchTransactionDTO, DishonoredAchTransactionDTO, BookTransactionDTO, PurchaseTransactionDTO, AtmTransactionDTO, FeeTransactionDTO, CardTransactionDTO, @@ -445,7 +481,7 @@ def from_json_api(_id, _type, attributes, relationships): InterestTransactionDTO, DisputeTransactionDTO, CheckDepositTransactionDTO, ReturnedCheckDepositTransactionDTO, CheckPaymentTransactionDTO, ReturnedCheckPaymentTransactionDTO, PaymentAdvanceTransactionDTO, - RepaidPaymentAdvanceTransactionDTO] + RepaidPaymentAdvanceTransactionDTO, AccountLowBalanceClosureTransactionDTO, NegativeBalanceCoverageTransactionDTO] class PatchTransactionRequest(BaseTransactionDTO, UnitRequest): From fe9abfe1cc16b907db66c5edb5b90efab097dd42 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Wed, 28 May 2025 11:22:54 -0400 Subject: [PATCH 151/181] Update returned object for from_json_api (#45) Co-authored-by: Julia Park --- unit/models/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 10e58057..64092e2f 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -254,7 +254,7 @@ def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Op @staticmethod def from_json_api(_id, _type, attributes, relationships): - return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + return CheckDepositPendingReviewEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], attributes.get("tags"), relationships) @@ -267,7 +267,7 @@ def __init__(self, id: str, created_at: datetime, previous_status: str, tags: Op @staticmethod def from_json_api(_id, _type, attributes, relationships): - return CheckDepositCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + return CheckDepositPendingEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], attributes.get("tags"), relationships) From d07d041b1624252754179c53d9fe9651c931a4d3 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Thu, 29 May 2025 17:03:14 -0400 Subject: [PATCH 152/181] refactor: remove unnecessary args from dto --- unit/models/transaction.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 57274c60..8d66e046 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -439,40 +439,27 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("tags"), relationships) class AccountLowBalanceClosureTransactionDTO(BaseTransactionDTO): - def __init__(self, id: str, created_at: datetime, receiver_counterparty: Dict[str, str], amount: int, direction: str, + 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' - self.attributes["receiverCounterparty"] = receiver_counterparty - self.attributes["amount"] = amount - self.attributes["direction"] = direction - self.attributes["balance"] = balance - self.attributes["summary"] = summary - self.attributes["tags"] = tags @staticmethod def from_json_api(_id, _type, attributes, relationships): return AccountLowBalanceClosureTransactionDTO( - _id, date_utils.to_datetime(attributes["createdAt"]), attributes["receiverCounterparty"], - attributes["amount"], attributes["direction"], attributes["balance"], attributes["summary"], - attributes.get("tags"), relationships) + _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' - self.attributes["amount"] = amount - self.attributes["direction"] = direction - self.attributes["balance"] = balance - self.attributes["summary"] = summary - self.attributes["tags"] = tags @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) + _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, From ef749f3fc92b11e69152774ff5f57662c678361e Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Tue, 10 Jun 2025 07:20:35 -0400 Subject: [PATCH 153/181] feat: add received payment marked for return event --- unit/__init__.py | 2 ++ unit/models/codecs.py | 2 ++ unit/models/event.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/unit/__init__.py b/unit/__init__.py index 7be2e411..8c404483 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -27,6 +27,7 @@ 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 __all__ = ["api", "models", "utils"] @@ -62,3 +63,4 @@ def __init__(self, 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) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index b8fbec3a..9b21cbf8 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -370,6 +370,8 @@ "negativeBalanceCoverageTransaction": lambda _id, _type, attributes, relationships: NegativeBalanceCoverageTransactionDTO.from_json_api(_id, _type, attributes, relationships), + "receivedPayment.markedForReturn": lambda _id, _type, attributes, relationships: + ReceivedPaymentMarkedForReturnEvent.from_json_api(_id, _type, attributes, relationships), } diff --git a/unit/models/event.py b/unit/models/event.py index 64092e2f..9b1fa48b 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -761,6 +761,48 @@ 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 ReceivedPaymentMarkedForReturnEvent(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: datetime, + can_be_reprocessed: str, + addenda: 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.markedForReturn' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return ReceivedPaymentMarkedForReturnEvent(_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["returnCutoffTime"], attributes["canBeReprocessed"], + attributes["addenda"], attributes.get("tags"), relationships) + EventDTO = Union[ AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, From 0159679c326e825cc869473cab923b886e299c6d Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Wed, 11 Jun 2025 11:35:55 -0400 Subject: [PATCH 154/181] feat: add reprocess fields and resources --- unit/api/received_payment_resource.py | 8 ++++++++ unit/models/payment.py | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) 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/models/payment.py b/unit/models/payment.py index 984c69ba..971b1731 100644 --- a/unit/models/payment.py +++ b/unit/models/payment.py @@ -149,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 @@ -165,7 +167,8 @@ 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], From 995c1339f725c330c6a6acee174829dac35f7982 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Wed, 11 Jun 2025 17:24:46 -0400 Subject: [PATCH 155/181] refactor: rename ReceivedPaymentMarkedForReturn to ReceivedPaymentCreated event --- unit/models/codecs.py | 4 ++-- unit/models/event.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 9b21cbf8..6e789508 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -370,8 +370,8 @@ "negativeBalanceCoverageTransaction": lambda _id, _type, attributes, relationships: NegativeBalanceCoverageTransactionDTO.from_json_api(_id, _type, attributes, relationships), - "receivedPayment.markedForReturn": lambda _id, _type, attributes, relationships: - ReceivedPaymentMarkedForReturnEvent.from_json_api(_id, _type, attributes, relationships), + "receivedPayment.created": lambda _id, _type, attributes, relationships: + ReceivedPaymentCreatedEvent.from_json_api(_id, _type, attributes, relationships), } diff --git a/unit/models/event.py b/unit/models/event.py index 9b1fa48b..5fe5df4b 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -761,7 +761,7 @@ 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 ReceivedPaymentMarkedForReturnEvent(BaseEvent): +class ReceivedPaymentCreatedEvent(BaseEvent): def __init__(self, id: str, created_at: datetime, status: str, type: str, @@ -791,11 +791,11 @@ def __init__(self, id: str, created_at: datetime, self.attributes["canBeReprocessed"] = can_be_reprocessed self.attributes["addenda"] = addenda - self.type = 'receivedPayment.markedForReturn' + self.type = 'receivedPayment.created' @staticmethod def from_json_api(_id, _type, attributes, relationships): - return ReceivedPaymentMarkedForReturnEvent(_id, date_utils.to_datetime(attributes["createdAt"]), + return ReceivedPaymentCreatedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["status"], attributes["type"], attributes["amount"], attributes["completionDate"], attributes["companyName"], attributes["counterpartyRoutingNumber"], attributes["description"], @@ -820,7 +820,7 @@ def from_json_api(_id, _type, attributes, relationships): CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, - DisputeCreatedEvent, DisputeStatusChangedEvent, + DisputeCreatedEvent, DisputeStatusChangedEvent, ReceivedPaymentCreatedEvent ] From 16520ea612a44157856fa834951ba2d1c135908e Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Mon, 16 Jun 2025 17:03:46 -0400 Subject: [PATCH 156/181] fix: optional fields on received payment created event --- unit/models/event.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 5fe5df4b..17d421f2 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -772,9 +772,9 @@ def __init__(self, id: str, created_at: datetime, description: str, trace_number: str, sec_code: str, - return_cutoff_time: datetime, - can_be_reprocessed: str, - addenda: 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) @@ -800,8 +800,8 @@ def from_json_api(_id, _type, attributes, relationships): attributes["completionDate"], attributes["companyName"], attributes["counterpartyRoutingNumber"], attributes["description"], attributes["traceNumber"], attributes["secCode"], - attributes["returnCutoffTime"], attributes["canBeReprocessed"], - attributes["addenda"], attributes.get("tags"), relationships) + attributes.get("returnCutoffTime"), attributes.get("canBeReprocessed"), + attributes.get("addenda"), attributes.get("tags"), relationships) EventDTO = Union[ From f4ae2365a485750ac53f807dea50c9ca12ef029a Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Fri, 18 Jul 2025 06:50:22 -0400 Subject: [PATCH 157/181] Update access for attributes for AuthorizationRequestDeclinedEvent (#47) Co-authored-by: Julia Park --- unit/models/event.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 17d421f2..a683947b 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -151,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' @@ -167,9 +167,9 @@ 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) From d1aa75e87636339c6c628b165159573b7972548b Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Tue, 22 Jul 2025 16:52:22 -0400 Subject: [PATCH 158/181] feat: add jwt subject to authorized user entity --- unit/models/__init__.py | 5 +++-- unit/models/codecs.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index aef251eb..19c3d2c3 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -332,10 +332,11 @@ def from_json_api(l: List): class AuthorizedUser(UnitDTO): - def __init__(self, full_name: FullName, email: str, phone: Phone): + 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) -> List: @@ -343,7 +344,7 @@ def from_json_api(l: List) -> List: for data in l: authorized_users.append( AuthorizedUser( - data.get("fullName"), data.get("email"), data.get("phone") + data.get("fullName"), data.get("email"), data.get("phone"), data.get("jwtSubject") ) ) return authorized_users diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 6e789508..6f85cef8 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -459,7 +459,7 @@ def default(self, obj): if isinstance(obj, BusinessContact): return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} if isinstance(obj, AuthorizedUser): - return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} + return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone, "jwtSubject": obj.jwt_subject} 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} From ed366d29115b39b169fc86ca2e91b4a7d451efb2 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Wed, 23 Jul 2025 10:09:35 -0400 Subject: [PATCH 159/181] feat: add optional jwt_token field to CreateCustomerToken DTO --- unit/models/customerToken.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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): From 4e56161236986c9b97c94a406e9a9cefd90f1644 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Wed, 23 Jul 2025 11:26:16 -0400 Subject: [PATCH 160/181] feat: make jwtSubject field optional in authorized user serialization --- unit/models/codecs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 6f85cef8..aed24b23 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -459,7 +459,10 @@ def default(self, obj): if isinstance(obj, BusinessContact): return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone} if isinstance(obj, AuthorizedUser): - return {"fullName": obj.full_name, "email": obj.email, "phone": obj.phone, "jwtSubject": obj.jwt_subject} + 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} From caefec61be89669d8cd762f28ebe92530fc1cfd2 Mon Sep 17 00:00:00 2001 From: in2q Date: Mon, 4 Aug 2025 08:11:34 -0700 Subject: [PATCH 161/181] feat: add card fraud case event models Add support for 5 new card fraud case event types: - CardFraudCaseCreatedEvent (card.fraudCase.created) - CardFraudCaseActivatedEvent (card.fraudCase.activated) - CardFraudCaseExpiredEvent (card.fraudCase.expired) - CardFraudCaseFraudEvent (card.fraudCase.fraud) - CardFraudCaseNoFraudEvent (card.fraudCase.noFraud) Each event includes status, decision, activityType, and expiresAt attributes to support comprehensive fraud case handling. --- unit/models/event.py | 92 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/unit/models/event.py b/unit/models/event.py index a683947b..72252ad4 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -232,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 = 'card.fraudCase.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 = 'card.fraudCase.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 = 'card.fraudCase.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 = 'card.fraudCase.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 = 'card.fraudCase.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]]): @@ -807,6 +897,8 @@ def from_json_api(_id, _type, attributes, relationships): EventDTO = Union[ AccountClosedEvent, AccountFrozenEvent, ApplicationDeniedEvent, ApplicationAwaitingDocumentsEvent, ApplicationPendingReviewEvent, CardActivatedEvent, CardStatusChangedEvent, + CardFraudCaseCreatedEvent, CardFraudCaseActivatedEvent, CardFraudCaseExpiredEvent, + CardFraudCaseFraudEvent, CardFraudCaseNoFraudEvent, AuthorizationCreatedEvent, AuthorizationCanceledEvent, AuthorizationDeclinedEvent, AuthorizationRequestDeclinedEvent, AuthorizationRequestPendingEvent, AuthorizationRequestApprovedEvent, DocumentApprovedEvent, DocumentRejectedEvent, From 959a163b3a7c249d1f0c6888cfea369c4aabbede Mon Sep 17 00:00:00 2001 From: in2q Date: Wed, 6 Aug 2025 18:13:23 -0700 Subject: [PATCH 162/181] Nixed punctuation, updated to proper event names. --- unit/models/event.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 72252ad4..a9d2e286 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -236,7 +236,7 @@ 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 = 'card.fraudCase.created' + self.type = 'cardFraudCase.created' self.attributes["status"] = status self.attributes["decision"] = decision self.attributes["activityType"] = activity_type @@ -254,7 +254,7 @@ 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 = 'card.fraudCase.activated' + self.type = 'cardFraudCase.activated' self.attributes["status"] = status self.attributes["decision"] = decision self.attributes["activityType"] = activity_type @@ -272,7 +272,7 @@ 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 = 'card.fraudCase.expired' + self.type = 'cardFraudCase.expired' self.attributes["status"] = status self.attributes["decision"] = decision self.attributes["activityType"] = activity_type @@ -290,7 +290,7 @@ 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 = 'card.fraudCase.fraud' + self.type = 'cardFraudCase.fraud' self.attributes["status"] = status self.attributes["decision"] = decision self.attributes["activityType"] = activity_type @@ -308,7 +308,7 @@ 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 = 'card.fraudCase.noFraud' + self.type = 'cardFraudCase.noFraud' self.attributes["status"] = status self.attributes["decision"] = decision self.attributes["activityType"] = activity_type From 7c4c0da9e645f63f202e4375b85f2aa4e525c878 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:32:58 -0400 Subject: [PATCH 163/181] Add operating address to application dtos (#51) Co-authored-by: Julia Park --- unit/models/application.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/unit/models/application.py b/unit/models/application.py index 1fd706e5..e9efe1ed 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -140,6 +140,7 @@ def __init__( dba: Optional[str], sole_proprietorship: Optional[bool], business_vertical: Optional[BusinessVertical], + operating_address: Optional[Address], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]], ): @@ -160,6 +161,7 @@ def __init__( "dba": dba, "soleProprietorship": sole_proprietorship, "businessVertical": business_vertical, + "operatingAddress": operating_address, "tags": tags, } self.relationships = relationships @@ -182,6 +184,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("dba"), attributes.get("soleProprietorship"), attributes.get("businessVertical"), + attributes.get("operatingAddress"), attributes.get("tags"), relationships, ) @@ -213,6 +216,7 @@ def __init__( 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]], ): @@ -241,6 +245,7 @@ def __init__( "contact": contact, "officer": officer, "beneficialOwners": beneficial_owners, + "operatingAddress": operating_address, "tags": tags, } self.relationships = relationships @@ -271,6 +276,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("number_of_employees"), attributes.get("cash_flow"), attributes.get("countries_of_operation"), + attributes.get("operating_address"), attributes.get("tags"), relationships, ) @@ -301,6 +307,7 @@ def __init__( annual_revenue: Optional[AnnualRevenue] = None, number_of_employees: Optional[NumberOfEmployees] = None, business_vertical: Optional[BusinessVertical] = None, + operating_address: Optional[Address] = None, ): self.full_name = full_name self.date_of_birth = date_of_birth @@ -321,6 +328,7 @@ def __init__( self.number_of_employees = number_of_employees self.business_vertical = business_vertical self.website = website + self.operating_address = operating_address def to_json_api(self) -> Dict: payload = { @@ -382,6 +390,9 @@ def to_json_api(self) -> Dict: if self.business_vertical: payload["data"]["attributes"]["businessVertical"] = self.business_vertical + if self.operating_address: + payload["data"]["attributes"]["operatingAddress"] = self.operating_address + return payload def __repr__(self): @@ -412,7 +423,8 @@ def __init__( countries_of_operation: Optional[List[str]] = None, stock_symbol: Optional[str] = None, business_vertical: Optional[BusinessVertical] = None, - device_fingerprints: Optional[List[DeviceFingerprint]] = None + device_fingerprints: Optional[List[DeviceFingerprint]] = None, + operating_address: Optional[Address] = None, ): self.name = name self.address = address @@ -436,6 +448,7 @@ def __init__( self.stock_symbol = stock_symbol self.business_vertical = business_vertical self.device_fingerprints = device_fingerprints + self.operating_address = operating_address def to_json_api(self) -> dict: payload = { @@ -487,6 +500,9 @@ def to_json_api(self) -> dict: if self.stock_symbol: payload["data"]["attributes"]["stockSymbol"] = self.stock_symbol + if self.operating_address: + payload["data"]["attributes"]["operatingAddress"] = self.operating_address + return payload def __repr__(self): From 17f8bc5c1d6eb81608c3c9f3936dec03b43a8e20 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:42:15 -0400 Subject: [PATCH 164/181] Remove operatingAddress from individual applications (#52) Co-authored-by: Julia Park --- unit/models/application.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/unit/models/application.py b/unit/models/application.py index e9efe1ed..e1c2edf3 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -140,7 +140,6 @@ def __init__( dba: Optional[str], sole_proprietorship: Optional[bool], business_vertical: Optional[BusinessVertical], - operating_address: Optional[Address], tags: Optional[Dict[str, str]], relationships: Optional[Dict[str, Relationship]], ): @@ -161,7 +160,6 @@ def __init__( "dba": dba, "soleProprietorship": sole_proprietorship, "businessVertical": business_vertical, - "operatingAddress": operating_address, "tags": tags, } self.relationships = relationships @@ -184,7 +182,6 @@ def from_json_api(_id, _type, attributes, relationships): attributes.get("dba"), attributes.get("soleProprietorship"), attributes.get("businessVertical"), - attributes.get("operatingAddress"), attributes.get("tags"), relationships, ) @@ -307,7 +304,6 @@ def __init__( annual_revenue: Optional[AnnualRevenue] = None, number_of_employees: Optional[NumberOfEmployees] = None, business_vertical: Optional[BusinessVertical] = None, - operating_address: Optional[Address] = None, ): self.full_name = full_name self.date_of_birth = date_of_birth @@ -328,7 +324,6 @@ def __init__( self.number_of_employees = number_of_employees self.business_vertical = business_vertical self.website = website - self.operating_address = operating_address def to_json_api(self) -> Dict: payload = { @@ -390,9 +385,6 @@ def to_json_api(self) -> Dict: if self.business_vertical: payload["data"]["attributes"]["businessVertical"] = self.business_vertical - if self.operating_address: - payload["data"]["attributes"]["operatingAddress"] = self.operating_address - return payload def __repr__(self): From 10ac731b557e51ac7555622f7a64d381d791b109 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 12:58:51 -0400 Subject: [PATCH 165/181] applications/check-registered-agent-address --- unit/api/application_resource.py | 14 +++++++ unit/api/check_registered_address.py | 21 ++++++++++ unit/models/check_registered_address.py | 51 +++++++++++++++++++++++++ unit/models/event.py | 1 + 4 files changed, 87 insertions(+) create mode 100644 unit/api/check_registered_address.py create mode 100644 unit/models/check_registered_address.py diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 82d041e7..21cef727 100644 --- a/unit/api/application_resource.py +++ b/unit/api/application_resource.py @@ -71,6 +71,20 @@ def update(self, request: PatchApplicationRequest) -> Union[UnitResponse[Applica else: return UnitError.from_json_api(response.json()) + def check_registered_agent_address(self, request: Union[CreateIndividualApplicationRequest, CreateBusinessApplicationRequest]) -> Union[UnitResponse[ApplicationDTO], UnitError]: + payload = request.to_json_api() + response = super().post(f"{self.resource}/check-registered-agent-address", payload) + + if response.ok: + data = response.json().get("data") + included = response.json().get("included") + 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()) + def approve_sb(self, request: ApproveApplicationSBRequest): url = f"sandbox/{self.resource}/{request.application_id}/approve" diff --git a/unit/api/check_registered_address.py b/unit/api/check_registered_address.py new file mode 100644 index 00000000..dfdd27a5 --- /dev/null +++ b/unit/api/check_registered_address.py @@ -0,0 +1,21 @@ +from unit.api.base_resource import BaseResource +from unit.models.application 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/models/check_registered_address.py b/unit/models/check_registered_address.py new file mode 100644 index 00000000..e61713ad --- /dev/null +++ b/unit/models/check_registered_address.py @@ -0,0 +1,51 @@ +from unit.models import * + + + +class CheckRegisteredAddressRequest(UnitDTO): + def __init__( + self, + street: str, + city: str, + state: str, + postal_code: str, + country: str, + street2: Optional[str] = None, + ): + self.type = "checkRegisteredAgentAddress" + + self.attributes = { + "street": street, + "city": city, + "state": state, + "postalCode": postal_code, + "country": country, + "street2": street2, + } + + def to_json_api(self) -> Dict: + payload = { + "data": { + "type": "individualApplication", + "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(data: Dict): + return CheckRegisteredAddressRequest( + data.get("isRegisteredAgentAddress"), + ) diff --git a/unit/models/event.py b/unit/models/event.py index a9d2e286..ace181e7 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -913,6 +913,7 @@ def from_json_api(_id, _type, attributes, relationships): StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, DisputeCreatedEvent, DisputeStatusChangedEvent, ReceivedPaymentCreatedEvent + ] From 2298c92ed23eb9b69d9e4495ac891d397688037c Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 13:00:34 -0400 Subject: [PATCH 166/181] remove unused code --- unit/api/application_resource.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/unit/api/application_resource.py b/unit/api/application_resource.py index 21cef727..82d041e7 100644 --- a/unit/api/application_resource.py +++ b/unit/api/application_resource.py @@ -71,20 +71,6 @@ def update(self, request: PatchApplicationRequest) -> Union[UnitResponse[Applica else: return UnitError.from_json_api(response.json()) - def check_registered_agent_address(self, request: Union[CreateIndividualApplicationRequest, CreateBusinessApplicationRequest]) -> Union[UnitResponse[ApplicationDTO], UnitError]: - payload = request.to_json_api() - response = super().post(f"{self.resource}/check-registered-agent-address", payload) - - if response.ok: - data = response.json().get("data") - included = response.json().get("included") - 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()) - def approve_sb(self, request: ApproveApplicationSBRequest): url = f"sandbox/{self.resource}/{request.application_id}/approve" From c2861674bb676641dd52dffecbc68dcd54654564 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 13:01:13 -0400 Subject: [PATCH 167/181] Remove blank space --- unit/models/event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index ace181e7..a9d2e286 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -913,7 +913,6 @@ def from_json_api(_id, _type, attributes, relationships): StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, DisputeCreatedEvent, DisputeStatusChangedEvent, ReceivedPaymentCreatedEvent - ] From b7c19039d882be95f7d9b5cba0094c7232a85097 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 13:17:13 -0400 Subject: [PATCH 168/181] MR Feedback --- unit/__init__.py | 2 ++ unit/api/check_registered_address.py | 2 +- unit/models/check_registered_address.py | 10 ++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/unit/__init__.py b/unit/__init__.py index 8c404483..371846ce 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -28,6 +28,7 @@ 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"] @@ -64,3 +65,4 @@ def __init__(self, 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/check_registered_address.py b/unit/api/check_registered_address.py index dfdd27a5..b6ae299e 100644 --- a/unit/api/check_registered_address.py +++ b/unit/api/check_registered_address.py @@ -1,5 +1,5 @@ from unit.api.base_resource import BaseResource -from unit.models.application import * +from unit.models import * from unit.models.check_registered_address import CheckRegisteredAddressRequest, CheckRegisteredAddressResponse from unit.models.codecs import DtoDecoder diff --git a/unit/models/check_registered_address.py b/unit/models/check_registered_address.py index e61713ad..2b9f0f46 100644 --- a/unit/models/check_registered_address.py +++ b/unit/models/check_registered_address.py @@ -20,9 +20,11 @@ def __init__( "state": state, "postalCode": postal_code, "country": country, - "street2": street2, } + if street2: + self.attributes["street2"] = street2 + def to_json_api(self) -> Dict: payload = { "data": { @@ -45,7 +47,7 @@ def __init__( } @staticmethod - def from_json_api(data: Dict): - return CheckRegisteredAddressRequest( - data.get("isRegisteredAgentAddress"), + def from_json_api(_id, _type, attributes, relationships): + return CheckRegisteredAddressResponse( + attributes.get("isRegisteredAgentAddress"), ) From 3f02a895da2eeb1c9dc1e1168925177a10a24aae Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 13:18:16 -0400 Subject: [PATCH 169/181] Fix type --- unit/models/check_registered_address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/check_registered_address.py b/unit/models/check_registered_address.py index 2b9f0f46..14451de6 100644 --- a/unit/models/check_registered_address.py +++ b/unit/models/check_registered_address.py @@ -28,7 +28,7 @@ def __init__( def to_json_api(self) -> Dict: payload = { "data": { - "type": "individualApplication", + "type": self.type, "attributes": self.attributes, } } From 5b7d74fab7e0f4504ca15d79eca7337015e4e190 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 15:08:26 -0400 Subject: [PATCH 170/181] Inherit request --- unit/models/check_registered_address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/models/check_registered_address.py b/unit/models/check_registered_address.py index 14451de6..d453b4e6 100644 --- a/unit/models/check_registered_address.py +++ b/unit/models/check_registered_address.py @@ -2,7 +2,7 @@ -class CheckRegisteredAddressRequest(UnitDTO): +class CheckRegisteredAddressRequest(UnitRequest): def __init__( self, street: str, From d0109357edcd6b902509fcec56dd918877135025 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Wed, 22 Oct 2025 15:56:25 -0400 Subject: [PATCH 171/181] Fix attrs --- unit/models/check_registered_address.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/unit/models/check_registered_address.py b/unit/models/check_registered_address.py index d453b4e6..6862a4c8 100644 --- a/unit/models/check_registered_address.py +++ b/unit/models/check_registered_address.py @@ -15,15 +15,17 @@ def __init__( self.type = "checkRegisteredAgentAddress" self.attributes = { - "street": street, - "city": city, - "state": state, - "postalCode": postal_code, - "country": country, + "address": { + "street": street, + "city": city, + "state": state, + "postalCode": postal_code, + "country": country, + } } if street2: - self.attributes["street2"] = street2 + self.attributes["address"]["street2"] = street2 def to_json_api(self) -> Dict: payload = { From 005039dbf27be4106cac67ed049c78da33c90918 Mon Sep 17 00:00:00 2001 From: Samuel Vasco Date: Mon, 27 Oct 2025 11:11:03 -0400 Subject: [PATCH 172/181] feat: add idempotency key to create business applications --- unit/models/application.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unit/models/application.py b/unit/models/application.py index e1c2edf3..06516cf0 100644 --- a/unit/models/application.py +++ b/unit/models/application.py @@ -417,6 +417,7 @@ def __init__( 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 @@ -441,6 +442,7 @@ def __init__( 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: payload = { @@ -494,6 +496,9 @@ def to_json_api(self) -> dict: if self.operating_address: payload["data"]["attributes"]["operatingAddress"] = self.operating_address + + if self.idempotency_key: + payload["data"]["attributes"]["idempotencyKey"] = self.idempotency_key return payload From 435fedecd77f3fc917d48d3a9d2901df32bc0f46 Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 30 Oct 2025 16:19:16 -0400 Subject: [PATCH 173/181] Handle absent merchants in adjustment transactions --- unit/models/__init__.py | 5 ++++- unit/models/transaction.py | 12 +++++++----- unit_python_sdk.egg-info/PKG-INFO | 12 +++++++++++- unit_python_sdk.egg-info/SOURCES.txt | 2 ++ 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/unit/models/__init__.py b/unit/models/__init__.py index 19c3d2c3..88e69d0f 100644 --- a/unit/models/__init__.py +++ b/unit/models/__init__.py @@ -444,7 +444,10 @@ def __init__( self.id = _id @staticmethod - def from_json_api(data: Dict): + 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") ) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 8d66e046..4f5ed862 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -131,7 +131,7 @@ def from_json_api(_id, _type, attributes, 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: Optional[Coordinates], + 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, @@ -142,7 +142,8 @@ def __init__(self, id: str, created_at: datetime, direction: str, amount: int, b 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 @@ -219,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 @@ -239,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"), diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO index f8e13e2e..c11c3a0c 100644 --- a/unit_python_sdk.egg-info/PKG-INFO +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -1,4 +1,4 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 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/ @@ -17,3 +17,13 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 License-File: LICENSE Requires-Dist: requests +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: download-url +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: requires-dist +Dynamic: summary diff --git a/unit_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt index f4f690c1..1b8d9a51 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -19,6 +19,7 @@ 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_registered_address.py unit/api/check_stop_payment_resource.py unit/api/counterparty_resource.py unit/api/customerToken_resource.py @@ -50,6 +51,7 @@ unit/models/bill_pay.py unit/models/card.py unit/models/check_deposit.py unit/models/check_payment.py +unit/models/check_registered_address.py unit/models/check_stop_payment.py unit/models/codecs.py unit/models/counterparty.py From bfab1143e6712d26038ee5b596a0b6250913bbcb Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Thu, 30 Oct 2025 16:21:33 -0400 Subject: [PATCH 174/181] Revert sources/package info changes --- unit_python_sdk.egg-info/PKG-INFO | 12 +----------- unit_python_sdk.egg-info/SOURCES.txt | 2 -- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO index c11c3a0c..f8e13e2e 100644 --- a/unit_python_sdk.egg-info/PKG-INFO +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -1,4 +1,4 @@ -Metadata-Version: 2.4 +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/ @@ -17,13 +17,3 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 License-File: LICENSE Requires-Dist: requests -Dynamic: author -Dynamic: author-email -Dynamic: classifier -Dynamic: download-url -Dynamic: home-page -Dynamic: keywords -Dynamic: license -Dynamic: license-file -Dynamic: requires-dist -Dynamic: summary diff --git a/unit_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt index 1b8d9a51..f4f690c1 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -19,7 +19,6 @@ 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_registered_address.py unit/api/check_stop_payment_resource.py unit/api/counterparty_resource.py unit/api/customerToken_resource.py @@ -51,7 +50,6 @@ unit/models/bill_pay.py unit/models/card.py unit/models/check_deposit.py unit/models/check_payment.py -unit/models/check_registered_address.py unit/models/check_stop_payment.py unit/models/codecs.py unit/models/counterparty.py From 510df64c12fa0efd7b9c879cc1c36936c572b585 Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:17:00 -0500 Subject: [PATCH 175/181] Define write off transaction dto (#56) Co-authored-by: Julia Park --- unit/models/codecs.py | 3 +++ unit/models/transaction.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index aed24b23..788f90d7 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -372,6 +372,9 @@ "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), } diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 4f5ed862..6fa0a88e 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -463,6 +463,18 @@ 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, From 7cfb15d27c69dff9144a898ade0721dc2fea605c Mon Sep 17 00:00:00 2001 From: Julia P <142916316+julia-truss@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:46:15 -0500 Subject: [PATCH 176/181] Include writeoffs in transaction dto (#57) Co-authored-by: Julia Park --- unit/models/transaction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unit/models/transaction.py b/unit/models/transaction.py index 6fa0a88e..59e2d17e 100644 --- a/unit/models/transaction.py +++ b/unit/models/transaction.py @@ -482,7 +482,8 @@ def from_json_api(_id, _type, attributes, relationships): InterestTransactionDTO, DisputeTransactionDTO, CheckDepositTransactionDTO, ReturnedCheckDepositTransactionDTO, CheckPaymentTransactionDTO, ReturnedCheckPaymentTransactionDTO, PaymentAdvanceTransactionDTO, - RepaidPaymentAdvanceTransactionDTO, AccountLowBalanceClosureTransactionDTO, NegativeBalanceCoverageTransactionDTO] + RepaidPaymentAdvanceTransactionDTO, AccountLowBalanceClosureTransactionDTO, NegativeBalanceCoverageTransactionDTO, + WriteOffTransactionDTO] class PatchTransactionRequest(BaseTransactionDTO, UnitRequest): From 67133c6e8c66c8cc533957b8a46f9e24d5e9e032 Mon Sep 17 00:00:00 2001 From: Samuel Vasco <52541302+samuelvasco@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:55:45 -0500 Subject: [PATCH 177/181] ENG-2874: add date filters to list events (#58) * feat: add date filters to the list events endpoint * fix: offset type --- unit/models/event.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/unit/models/event.py b/unit/models/event.py index a9d2e286..191a3ddf 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -917,13 +917,26 @@ def from_json_api(_id, _type, attributes, relationships): 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 From 7034bdd56654f20c1358613bbb5bd62f16ed0697 Mon Sep 17 00:00:00 2001 From: Ben Wolfe Date: Wed, 25 Feb 2026 10:52:53 -0800 Subject: [PATCH 178/181] Expose interest earnings in Unit SDK (#60) * Add interest earned resource --- unit/__init__.py | 2 ++ unit/api/accrued_interest_resource.py | 18 ++++++++++++ unit/models/accrued_interest.py | 41 +++++++++++++++++++++++++++ unit/models/codecs.py | 4 +++ 4 files changed, 65 insertions(+) create mode 100644 unit/api/accrued_interest_resource.py create mode 100644 unit/models/accrued_interest.py diff --git a/unit/__init__.py b/unit/__init__.py index 371846ce..70d057fd 100644 --- a/unit/__init__.py +++ b/unit/__init__.py @@ -25,6 +25,7 @@ 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.accrued_interest_resource import AccruedInterestResource from unit.api.reward_resource import RewardResource from unit.api.dispute_resource import DisputeResource from unit.api.received_payment_resource import ReceivedPaymentResource @@ -59,6 +60,7 @@ 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.accrued_interest = AccruedInterestResource(api_url, token) self.rewards = RewardResource(api_url, token) self.batchRelease = BatchReleaseResource(api_url, token) self.check_payments = CheckPaymentResource(api_url, token) diff --git a/unit/api/accrued_interest_resource.py b/unit/api/accrued_interest_resource.py new file mode 100644 index 00000000..218e3cd8 --- /dev/null +++ b/unit/api/accrued_interest_resource.py @@ -0,0 +1,18 @@ +from unit.api.base_resource import BaseResource +from unit.models.accrued_interest import * +from unit.models.codecs import DtoDecoder + + +class AccruedInterestResource(BaseResource): + def __init__(self, api_url, token): + super().__init__(api_url, token) + self.resource = "accrued-interest" + + def total(self, params: GetAccruedInterestTotalParams = None) -> Union[UnitResponse[AccruedInterestTotalDTO], UnitError]: + params = params or GetAccruedInterestTotalParams() + response = super().get(f"{self.resource}/total", params.to_dict()) + if super().is_20x(response.status_code): + data = response.json().get("data") + return UnitResponse[AccruedInterestTotalDTO](DtoDecoder.decode(data), None) + else: + return UnitError.from_json_api(response.json()) diff --git a/unit/models/accrued_interest.py b/unit/models/accrued_interest.py new file mode 100644 index 00000000..7a43628c --- /dev/null +++ b/unit/models/accrued_interest.py @@ -0,0 +1,41 @@ +from typing import Optional +from unit.models import * + + +class AccruedInterestTotalDTO(object): + def __init__(self, id: str, amount: int, + relationships: Optional[Dict[str, Relationship]]): + self.id = id + self.type = "accruedInterestTotal" + self.attributes = {"amount": amount} + self.relationships = relationships + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return AccruedInterestTotalDTO(_id, attributes["amount"], relationships) + + +class GetAccruedInterestTotalParams(UnitParams): + def __init__(self, account_id: Optional[str] = None, + since: Optional[str] = None, until: Optional[str] = None, + since_interest_month: Optional[str] = None, + until_interest_month: Optional[str] = None): + self.account_id = account_id + self.since = since + self.until = until + self.since_interest_month = since_interest_month + self.until_interest_month = until_interest_month + + def to_dict(self) -> Dict: + parameters = {} + if self.account_id: + parameters["filter[accountId]"] = self.account_id + if self.since: + parameters["filter[since]"] = self.since + if self.until: + parameters["filter[until]"] = self.until + if self.since_interest_month: + parameters["filter[sinceInterestMonth]"] = self.since_interest_month + if self.until_interest_month: + parameters["filter[untilInterestMonth]"] = self.until_interest_month + return parameters diff --git a/unit/models/codecs.py b/unit/models/codecs.py index 788f90d7..fbd115de 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -28,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.accrued_interest import AccruedInterestTotalDTO from unit.models.benificial_owner import BenificialOwnerDTO mappings = { @@ -343,6 +344,9 @@ "accountEndOfDay": lambda _id, _type, attributes, relationships: AccountEndOfDayDTO.from_json_api(_id, _type, attributes, relationships), + "accruedInterestTotal": lambda _id, _type, attributes, relationships: + AccruedInterestTotalDTO.from_json_api(_id, _type, attributes, relationships), + "counterpartyBalance": lambda _id, _type, attributes, relationships: CounterpartyBalanceDTO.from_json_api(_id, _type, attributes, relationships), From 0390fb56861b2e66bc1726f522d69054a2731537 Mon Sep 17 00:00:00 2001 From: samuelvasco <52541302+samuelvasco@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:04:06 +0000 Subject: [PATCH 179/181] ENG-3084: add payment canceled event (#59) --- unit/models/codecs.py | 6 ++++++ unit/models/event.py | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/unit/models/codecs.py b/unit/models/codecs.py index fbd115de..1eb1cb31 100644 --- a/unit/models/codecs.py +++ b/unit/models/codecs.py @@ -302,6 +302,12 @@ "payment.returned": lambda _id, _type, attributes, relationships: PaymentReturnedEvent.from_json_api(_id, _type, attributes, relationships), + "payment.canceled": lambda _id, _type, attributes, relationships: + PaymentCanceledEvent.from_json_api(_id, _type, attributes, relationships), + + "payment.Canceled": lambda _id, _type, attributes, relationships: + PaymentCanceledUpperCasedEvent.from_json_api(_id, _type, attributes, relationships), + "payment.rejected": lambda _id, _type, attributes, relationships: PaymentRejectedEvent.from_json_api(_id, _type, attributes, relationships), diff --git a/unit/models/event.py b/unit/models/event.py index 191a3ddf..45d1e602 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -734,6 +734,26 @@ def from_json_api(_id, _type, attributes, relationships): return PaymentReturnedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes["previousStatus"], attributes.get("tags"), relationships) +class PaymentCanceledEvent(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 = 'payment.canceled' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentCanceledEvent(_id, date_utils.to_datetime(attributes["createdAt"]), attributes.get("tags"), relationships) + +class PaymentCanceledUpperCasedEvent(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 = 'payment.Canceled' + + @staticmethod + def from_json_api(_id, _type, attributes, relationships): + return PaymentCanceledUpperCasedEvent(_id, date_utils.to_datetime(attributes["createdAt"]), 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]]): @@ -909,7 +929,7 @@ def from_json_api(_id, _type, attributes, relationships): CheckPaymentDeliveredEvent, CheckPaymentReturnToSenderEvent, CheckPaymentCanceledEvent, CheckPaymentDeliveryStatusChangedEvent, CheckPaymentAdditionalVerificationRequiredEvent, CheckPaymentAdditionalVerificationApprovedEvent, - CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, + CustomerCreatedEvent, PaymentClearingEvent, PaymentSentEvent, PaymentReturnedEvent, PaymentCanceledEvent, PaymentCanceledUpperCasedEvent, StatementsCreatedEvent, TransactionCreatedEvent, AccountReopenedEvent, RawUnitObject, StopPaymentCreatedEvent, StopPaymentPaymentStoppedEvent, StopPaymentDisabledEvent, DisputeCreatedEvent, DisputeStatusChangedEvent, ReceivedPaymentCreatedEvent From fcaf8342cf8789dc9dbf8e7e68d8436c931a03bb Mon Sep 17 00:00:00 2001 From: Avery Kushner Date: Mon, 2 Mar 2026 11:45:09 -0500 Subject: [PATCH 180/181] Cursor skill update --- .cursor/rules/python-sdk.mdc | 34 ++++ .cursor/skills/local-install/SKILL.md | 14 ++ .cursor/skills/sdk-guide/SKILL.md | 281 ++++++++++++++++++++++++++ unit_python_sdk.egg-info/PKG-INFO | 12 +- unit_python_sdk.egg-info/SOURCES.txt | 4 + 5 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 .cursor/rules/python-sdk.mdc create mode 100644 .cursor/skills/local-install/SKILL.md create mode 100644 .cursor/skills/sdk-guide/SKILL.md diff --git a/.cursor/rules/python-sdk.mdc b/.cursor/rules/python-sdk.mdc new file mode 100644 index 00000000..7aad930c --- /dev/null +++ b/.cursor/rules/python-sdk.mdc @@ -0,0 +1,34 @@ +--- +alwaysApply: true +description: Coding conventions for the Unit Python SDK fork +--- + +## Unit Python SDK Conventions + +This is a fork of the Unit.co Python SDK. Follow the same Python style conventions as the API repo (keyword arguments, no docstrings, comments explain why not what). + +### Request Types + +- Every request type must implement `to_json_api()` returning a JSON API payload (`{"data": {"type": "...", "attributes": {...}}}`) +- Use keyword arguments in constructors +- Optional fields should default to `None` and be excluded from the serialized payload when not set + +### DTO Types + +- DTOs are response types instantiated by `DtoDecoder.decode()` from JSON API responses +- Each DTO has an `id` and an `attributes` dict +- DTOs are dataclass-style objects (plain `__init__` with attribute assignment) + +### Codec Registration + +When adding a new Unit API type: +1. Create the DTO class in `unit/models/` +2. Create the request class with `to_json_api()` in the same file +3. Add the JSON API type string -> DTO class mapping to `mappings` in `unit/models/codecs.py` +4. Add resource methods in `unit/api/` if needed + +### Resource Classes + +- All resource classes inherit from `BaseResource` (`unit/api/base_resource.py`) +- Use `self._post()`, `self._get()`, `self._patch()` etc. from BaseResource +- Resource methods return `UnitResponse` wrapper objects diff --git a/.cursor/skills/local-install/SKILL.md b/.cursor/skills/local-install/SKILL.md new file mode 100644 index 00000000..d8634b0b --- /dev/null +++ b/.cursor/skills/local-install/SKILL.md @@ -0,0 +1,14 @@ +--- +name: truss-unit-sdk-local-install +description: Install the local unit-python-sdk fork into the API virtualenv in editable mode so SDK changes are immediately reflected. Use when modifying unit-python-sdk, adding new SDK types, or after any edit to unit-python-sdk/unit/models/ that needs to be picked up by the API. +--- + +# Truss Unit SDK Local Install + +After modifying anything in `unit-python-sdk/`, run this to make the API venv pick up the changes: + +```bash +/Users/averykushner/cursor-projects/truss-fullstack/api/.venv/bin/pip install -e /Users/averykushner/cursor-projects/truss-fullstack/unit-python-sdk +``` + +The `-e` flag installs in editable mode — the venv symlinks to the local source, so subsequent edits to the SDK are reflected immediately without reinstalling. Only run this command again if the venv is recreated or the SDK package metadata changes (e.g. `pyproject.toml`). diff --git a/.cursor/skills/sdk-guide/SKILL.md b/.cursor/skills/sdk-guide/SKILL.md new file mode 100644 index 00000000..8439ce6c --- /dev/null +++ b/.cursor/skills/sdk-guide/SKILL.md @@ -0,0 +1,281 @@ +--- +name: truss-unit-sdk +description: Guide to the Truss Unit Python SDK fork covering the resource-based API pattern, application DTOs and request types, JSON API codec system, and how the API repo interfaces with the SDK via unit_api_call. Use when updating the Unit SDK, adding new SDK types, modifying application submission, working with Unit API responses, or preparing for Unit API version migrations. +--- + +# Truss Unit Python SDK + +## Overview + +Truss maintains a fork of the Unit Python SDK at `unit-python-sdk/`. This is a typed REST client for the [Unit.co](https://www.unit.co) banking API. The Truss API repo (`api/`) imports this SDK and wraps all calls through `unit_api_call()`. + +## Project Structure + +``` +unit-python-sdk/ +├── unit/ +│ ├── __init__.py # Unit client class — exposes resource accessors +│ ├── api/ # Resource classes (REST endpoint wrappers) +│ │ ├── base_resource.py # HTTP methods, auth headers, JSON API encoding +│ │ ├── application_resource.py # applications.create(), .get(), .list(), .update() +│ │ └── [30+ other resources] # payments, accounts, customers, etc. +│ ├── models/ # DTOs, request types, response types +│ │ ├── application.py # Application DTOs + Create/Update request types +│ │ ├── codecs.py # DtoDecoder — JSON API type → DTO class mapping +│ │ └── [30+ other model files] +│ └── utils/ # Utility functions +├── e2e_tests/ +│ └── application_test.py # Application e2e tests +└── setup.py # Package version (currently 0.10.5) +``` + +## SDK Architecture + +### Request → API → Response Flow + +``` +1. Build Request Object (e.g., CreateBusinessApplicationRequest) + │ + ↓ .to_json_api() +2. JSON API Payload {"data": {"type": "businessApplication", "attributes": {...}}} + │ + ↓ BaseResource.post(url, payload) +3. HTTP POST POST {api_url}/applications + │ Headers: Content-Type: application/vnd.api+json + │ Authorization: Bearer {token} + ↓ +4. Raw JSON Response {"data": {"type": "businessApplication", "id": "...", "attributes": {...}}} + │ + ↓ DtoDecoder.decode(response) +5. Typed DTO BusinessApplicationDTO(id=..., attributes=...) + │ + ↓ UnitResponse(data=dto, included=[...]) +6. UnitResponse Wrapper Final response with typed data + included resources +``` + +### BaseResource (`unit/api/base_resource.py`) + +All resource classes inherit from `BaseResource`. It provides: + +- `get(url)`, `post(url, data)`, `patch(url, data)`, `put(url, data)`, `delete(url)` +- Sets `Content-Type: application/vnd.api+json` and `Authorization: Bearer {token}` +- Serializes request objects via `UnitEncoder` (handles `to_json_api()` on request objects) +- Returns raw response dict for decoding + +### Unit Client (`unit/__init__.py`) + +The main `Unit` class instantiates all resource accessors: + +```python +class Unit: + def __init__(self, api_url, token): + self.applications = ApplicationResource(api_url, token) + self.payments = PaymentResource(api_url, token) + self.accounts = AccountResource(api_url, token) + # ... 30+ more resources +``` + +## Application Types + +### DTOs (Response Types) + +| Class | JSON API Type | Description | +|-------|---------------|-------------| +| `IndividualApplicationDTO` | `"individualApplication"` | Individual/sole prop application (includes `sole_proprietorship` flag) | +| `BusinessApplicationDTO` | `"businessApplication"` | Business application | + +Both are dataclass-style objects with an `id` (Unit's application ID) and `attributes` dict containing all application fields. + +### Request Types + +| Class | Creates | Key Fields | +|-------|---------|------------| +| `CreateIndividualApplicationRequest` | Individual application | full_name, dob, address, email, phone, ssn, sole_proprietorship, ip, tags, idempotency_key | +| `CreateBusinessApplicationRequest` | Business application | name, address, phone, ein, entity_type, state_of_incorporation, contact, officer, beneficial_owners, tags, idempotency_key | + +### Sole Prop as Individual + +Sole proprietorships are **not** a separate type. They use `CreateIndividualApplicationRequest` with `sole_proprietorship=True` and additional business-related fields: + +```python +CreateIndividualApplicationRequest( + full_name=FullName(first="John", last="Doe"), + sole_proprietorship=True, + # ... individual fields plus business fields +) +``` + +### `to_json_api()` — Serialization + +Each request type implements `to_json_api()` which returns the JSON API payload: + +```python +def to_json_api(self): + return { + "data": { + "type": "individualApplication", # or "businessApplication" + "attributes": { + # All fields serialized here + } + } + } +``` + +This is called automatically by `UnitEncoder` during serialization in `BaseResource.post()`. + +## Codec System (`unit/models/codecs.py`) + +### Type Mapping + +`DtoDecoder.decode()` uses a `mappings` dict to route JSON API responses to the correct DTO class: + +```python +mappings = { + "individualApplication": IndividualApplicationDTO, + "businessApplication": BusinessApplicationDTO, + # ... 50+ other type mappings +} +``` + +When a response comes back with `"type": "businessApplication"`, the decoder instantiates `BusinessApplicationDTO` with the response data. + +### Adding New Types + +To support a new Unit API type: + +1. Create the DTO class in the appropriate `unit/models/*.py` file +2. Create the request class with `to_json_api()` method +3. Add the type string → DTO class mapping to `mappings` in `unit/models/codecs.py` +4. If needed, add a new resource method or resource class in `unit/api/` + +## Sub-types Used in Applications + +### FullName +```python +FullName(first="John", last="Doe") +``` + +### Phone +```python +Phone(country_code="1", number="5551234567") +``` + +### Address (UnitAddress in Truss API) +```python +Address(street="123 Main St", city="Austin", state="TX", postal_code="78701", country="US") +``` + +### Officer +```python +Officer( + full_name=FullName(...), + date_of_birth=date(...), + address=Address(...), + phone=Phone(...), + email="...", + ssn="...", # or passport/nationality for non-US + title="CEO", +) +``` + +### BeneficialOwner +```python +BeneficialOwner( + full_name=FullName(...), + date_of_birth=date(...), + address=Address(...), + phone=Phone(...), + email="...", + ssn="...", + percentage=50, # 25-100 +) +``` + +### BusinessContact +```python +BusinessContact( + full_name=FullName(...), + phone=Phone(...), + email="...", +) +``` + +## How the API Repo Interfaces with the SDK + +For the `unit_api_call()` wrapper pattern, error mapping, and client initialization details, see the `architecture` skill at `api/.cursor/skills/architecture/SKILL.md`. + +### Model-to-SDK Conversion + +The conversion from Django models to SDK request types happens in `api/src/api/operations/account/onboarding/application/submit_onboarding_application.py`. See the `truss-onboarding-architecture` skill for the complete field mapping. + +## Subagents + +### trace-sdk-application-types: Map SDK application types and fields + +**Type:** `explore` +**Model:** *(default)* +**When:** You need to understand the complete structure of Unit SDK application types — every field, sub-type, and serialization detail — typically before modifying the SDK for a new Unit API version. + +**Prompt:** + +> Map the complete structure of application types in the Truss Unit Python SDK fork at `unit-python-sdk/`. +> +> 1. **Request types**: Read `unit-python-sdk/unit/models/application.py`. For each request class (`CreateIndividualApplicationRequest`, `CreateBusinessApplicationRequest`): +> - Document every field with its type and whether it's required or optional +> - Read the `to_json_api()` method to understand exactly how each field is serialized +> - Note any conditional fields (e.g., sole_proprietorship flag changes which fields are included) +> +> 2. **DTO types**: In the same file, read `IndividualApplicationDTO` and `BusinessApplicationDTO`: +> - Document the response structure (id, type, attributes) +> - Document how the DTO is constructed from JSON API responses +> +> 3. **Sub-types**: Find and read the definitions of `FullName`, `Phone`, `Address`, `Officer`, `BeneficialOwner`, `BusinessContact`. These may be in `unit/models/application.py` or imported from other model files. Document their fields. +> +> 4. **Codec mapping**: Read `unit-python-sdk/unit/models/codecs.py`: +> - Find the `mappings` dict +> - Document which type strings map to which DTO classes for applications +> - Understand how `DtoDecoder.decode()` routes responses +> +> 5. **Resource methods**: Read `unit-python-sdk/unit/api/application_resource.py`: +> - Document each method (create, get, list, update, upload, approve_sb) +> - Note the URL patterns used for each method +> - Document return types +> +> Return: +> - Complete field schema for each request type (field name, type, required/optional, serialized JSON key) +> - Complete field schema for each DTO type +> - Sub-type field schemas +> - Codec type string → class mapping for applications +> - Resource method signatures and URL patterns + +### audit-sdk-for-v2-migration: Identify SDK changes needed for Unit API v2 + +**Type:** `explore` +**Model:** *(default)* +**When:** You are preparing to update the Unit SDK fork to support a new version of the Unit applications API and need to identify what must change. + +**Prompt:** + +> Audit the Truss Unit Python SDK fork for changes needed to support Unit Applications API v2. The SDK is at `unit-python-sdk/`. +> +> 1. **Current application code**: Read these files completely: +> - `unit-python-sdk/unit/models/application.py` — all DTOs and request types +> - `unit-python-sdk/unit/api/application_resource.py` — all API methods +> - `unit-python-sdk/unit/models/codecs.py` — type mappings +> - `unit-python-sdk/unit/__init__.py` — client resource initialization +> +> 2. **SDK patterns for other resources**: Read 2-3 other resource/model pairs to understand how the SDK handles different resource types. This helps identify reusable patterns: +> - Pick resources that have complex nested types (like `payment.py` or `account.py`) +> +> 3. **Test patterns**: Read `unit-python-sdk/e2e_tests/application_test.py` to understand how application tests are structured. +> +> 4. **How the API repo consumes the SDK**: Read these files to understand what the API repo depends on: +> - `api/src/api/operations/account/onboarding/application/submit_onboarding_application.py` +> - `api/src/api/utils/integrations/unit/unit.py` (unit_api_call wrapper) +> +> Return: +> - List of files that need modification +> - For each file, what specifically needs to change (new classes, modified methods, new mappings) +> - Backward compatibility concerns (can v1 and v2 coexist?) +> - Recommended approach: separate v2 classes vs. version parameter vs. extending existing classes +> - API repo callsites that reference SDK types and would need updating diff --git a/unit_python_sdk.egg-info/PKG-INFO b/unit_python_sdk.egg-info/PKG-INFO index f8e13e2e..c11c3a0c 100644 --- a/unit_python_sdk.egg-info/PKG-INFO +++ b/unit_python_sdk.egg-info/PKG-INFO @@ -1,4 +1,4 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 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/ @@ -17,3 +17,13 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 License-File: LICENSE Requires-Dist: requests +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: download-url +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: requires-dist +Dynamic: summary diff --git a/unit_python_sdk.egg-info/SOURCES.txt b/unit_python_sdk.egg-info/SOURCES.txt index f4f690c1..09594612 100644 --- a/unit_python_sdk.egg-info/SOURCES.txt +++ b/unit_python_sdk.egg-info/SOURCES.txt @@ -6,6 +6,7 @@ unit/__init__.py unit/api/__init__.py unit/api/account_end_of_day_resource.py unit/api/account_resource.py +unit/api/accrued_interest_resource.py unit/api/ach_resource.py unit/api/api_token_resource.py unit/api/applicationForm_resource.py @@ -19,6 +20,7 @@ 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_registered_address.py unit/api/check_stop_payment_resource.py unit/api/counterparty_resource.py unit/api/customerToken_resource.py @@ -38,6 +40,7 @@ unit/api/webhook_resource.py unit/models/__init__.py unit/models/account.py unit/models/account_end_of_day.py +unit/models/accrued_interest.py unit/models/api_token.py unit/models/application.py unit/models/applicationForm.py @@ -50,6 +53,7 @@ unit/models/bill_pay.py unit/models/card.py unit/models/check_deposit.py unit/models/check_payment.py +unit/models/check_registered_address.py unit/models/check_stop_payment.py unit/models/codecs.py unit/models/counterparty.py From d2883ccf6137ecaa600ab1f5ad35e5e30ca12890 Mon Sep 17 00:00:00 2001 From: avesk <13068169+avesk@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:15:18 +0000 Subject: [PATCH 181/181] fix: make traceNumber and secCode optional in ReceivedPaymentCreatedEvent (ENG-3789) (#67) --- unit/models/event.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unit/models/event.py b/unit/models/event.py index 45d1e602..f8d08998 100644 --- a/unit/models/event.py +++ b/unit/models/event.py @@ -880,8 +880,8 @@ def __init__(self, id: str, created_at: datetime, company_name: str, counterparty_routing_number: str, description: str, - trace_number: str, - sec_code: str, + trace_number: Optional[str], + sec_code: Optional[str], return_cutoff_time: Optional[datetime], can_be_reprocessed: Optional[bool], addenda: Optional[str], @@ -909,7 +909,7 @@ def from_json_api(_id, _type, attributes, relationships): attributes["status"], attributes["type"], attributes["amount"], attributes["completionDate"], attributes["companyName"], attributes["counterpartyRoutingNumber"], attributes["description"], - attributes["traceNumber"], attributes["secCode"], + attributes.get("traceNumber"), attributes.get("secCode"), attributes.get("returnCutoffTime"), attributes.get("canBeReprocessed"), attributes.get("addenda"), attributes.get("tags"), relationships)