Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 156 additions & 4 deletions checkout_sdk/accounts/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,57 @@ class DaySchedule(str, Enum):


class BusinessType(str, Enum):
INDIVIDUAL_OR_SOLE_PROPRIETORSHIP = 'individual_or_sole_proprietorship'
GENERAL_PARTNERSHIP = 'general_partnership'
LIMITED_PARTNERSHIP = 'limited_partnership'
SCOTTISH_LIMITED_PARTNERSHIP = 'scottish_limited_partnership'
PUBLIC_LIMITED_COMPANY = 'public_limited_company'
LIMITED_COMPANY = 'limited_company'
LIMITED_LIABILITY_CORPORATION = 'limited_liability_corporation'
PRIVATE_CORPORATION = 'private_corporation'
PUBLICLY_TRADED_CORPORATION = 'publicly_traded_corporation'
PROFESSIONAL_ASSOCIATION = 'professional_association'
UNINCORPORATED_ASSOCIATION = 'unincorporated_association'
AUTO_ENTREPRENEUR = 'auto_entrepreneur'
GOVERNMENT_AGENCY = 'government_agency'
NON_PROFIT_ENTITY = 'non_profit_entity'
TRUST = 'trust'
CLUB_OR_SOCIETY = 'club_or_society'
REGULATED_FINANCIAL_INSTITUTION = 'regulated_financial_institution'
CFTC_REGISTERED_ENTITY = 'cftc_registered_entity'
SEC_REGISTERED_ENTITY = 'sec_registered_entity'


class EntityRoles(str, Enum):
UBO = 'ubo'
LEGAL_REPRESENTATIVE = 'legal_representative'
AUTHORISED_SIGNATORY = 'authorised_signatory'
DIRECTOR = 'director'
CONTROL_PERSON = 'control_person'


class CompanyPosition(str, Enum):
CEO = 'ceo'
CFO = 'cfo'
COO = 'coo'
MANAGING_MEMBER = 'managing_member'
GENERAL_PARTNER = 'general_partner'
PRESIDENT = 'president'
VICE_PRESIDENT = 'vice_president'
TREASURER = 'treasurer'
OTHER_SENIOR_MANAGEMENT = 'other_senior_management'
OTHER_EXECUTIVE_OFFICER = 'other_executive_officer'
OTHER_NON_EXECUTIVE_NON_SENIOR = 'other_non_executive_non_senior'


class NationalIdType(str, Enum):
SSN = 'ssn'
ITIN = 'itin'
PASSPORT = 'passport'
DRIVING_LICENSE = 'driving_license'
NATIONAL_ID_CARD = 'national_id_card'
RESIDENCE_PERMIT = 'residence_permit'
OTHER = 'other'


class EntityEmailAddresses:
Expand Down Expand Up @@ -108,6 +147,11 @@ class ArticlesOfAssociationType(str, Enum):
ARTICLES_OF_ASSOCIATION = "articles_of_association"


class ArticlesOfAssociation:
type: ArticlesOfAssociationType
front: str


class BankVerificationType(str, Enum):
BANK_STATEMENT = 'bank_statement'

Expand Down Expand Up @@ -157,10 +201,19 @@ class FinancialVerification:
front: str


class FinancialStatementsType(str, Enum):
FINANCIAL_STATEMENTS = 'financial_statements'


class FinancialStatements:
type: FinancialStatementsType
front: str


class OnboardSubEntityDocuments:
identity_verification: EntityIdentificationDocument
company_verification: CompanyVerification
articles_of_association: ArticlesOfAssociationType
articles_of_association: ArticlesOfAssociation
bank_verification: BankVerification
shareholder_structure: ShareholderStructure
proof_of_legality: ProofOfLegality
Expand All @@ -170,9 +223,74 @@ class OnboardSubEntityDocuments:
additional_document3: AdditionalDocument
tax_verification: TaxVerification
financial_verification: FinancialVerification
financial_statements: FinancialStatements


class CertifiedAuthorisedSignatoryType(str, Enum):
POWER_OF_ATTORNEY = 'power_of_attorney'


class CertifiedAuthorisedSignatory:
type: CertifiedAuthorisedSignatoryType
front: str


class ProofOfResidentialAddressType(str, Enum):
PROOF_OF_ADDRESS = 'proof_of_address'


class ProofOfResidentialAddress:
type: ProofOfResidentialAddressType
front: str


class ProofOfRegistrationType(str, Enum):
EXTRACT_FROM_TRADE_REGISTER = 'extract_from_trade_register'
OTHER = 'other'


class ProofOfRegistration:
type: ProofOfRegistrationType
front: str


# The representative-level documents union (Accounts API v3.0) is a distinct, smaller shape than the
# entity-level OnboardSubEntityDocuments — per shared/swagger-latest.json it carries only these fields.
class RepresentativeDocuments:
identity_verification: EntityIdentificationDocument
certified_authorised_signatory: CertifiedAuthorisedSignatory
proof_of_residential_address: ProofOfResidentialAddress
proof_of_registration: ProofOfRegistration


class Citizenship:
type: str
country: Country


class RepresentativeIndividual:
first_name: str
middle_name: str
last_name: str
date_of_birth: DateOfBirth
place_of_birth: PlaceOfBirth
citizenships: list # Citizenship
national_id_type: NationalIdType
national_id_number: str
email_address: str
phone: Phone
address: Address


class EntityRepresentative:
# v3.0 (Accounts API v3.0)
id: str
individual: RepresentativeIndividual
roles: list # accounts.EntityRoles
company_position: CompanyPosition
ownership_percentage: int
documents: RepresentativeDocuments
# v2.0 only — deprecated; use `individual` for v3.0
first_name: str
middle_name: str
last_name: str
Expand All @@ -181,8 +299,6 @@ class EntityRepresentative:
phone: Phone
date_of_birth: DateOfBirth
place_of_birth: PlaceOfBirth
roles: list # accounts.EntityRoles
documents: OnboardSubEntityDocuments


class EntityFinancialDocuments:
Expand All @@ -199,6 +315,7 @@ class EntityFinancialDetails:


class DateOfIncorporation:
day: int
month: int
year: int

Expand All @@ -208,6 +325,8 @@ class Company:
business_type: BusinessType
legal_name: str
trading_name: str
additional_trading_names: list # str
is_registered_company: bool
date_of_incorporation: DateOfIncorporation
regulatory_licence_number: str
principal_address: Address
Expand Down Expand Up @@ -235,13 +354,26 @@ class Individual:
financial_details: EntityFinancialDetails


class ProcessingDetailsAch:
annual_ach_volume: int
average_ach_transaction_size: int
estimated_monthly_credit_volume: int
average_credit_amount: int


class ProcessingDetailsPayments:
ach: ProcessingDetailsAch


class ProcessingDetails:
settlement_country: str
target_countries: list # str
annual_processing_volume: int
average_transaction_value: int
average_order_fulfillment_time: int
highest_transaction_value: int
currency: Currency
payments: ProcessingDetailsPayments


class AdditionalInfo:
Expand All @@ -250,16 +382,36 @@ class AdditionalInfo:
field3: str


class AgreedTerms:
date: str
ip_address: str
name: str
email: str
version: str


class SchemaVersionHeader:
accept: str

def get_header_mappings(self) -> Dict[str, str]:
return {
'accept': 'Accept'
}


class OnboardEntityRequest:
reference: str
is_draft: bool
profile: Profile
contact_details: ContactDetails
company: Company
processing_details: ProcessingDetails
individual: Individual
agreed_terms: AgreedTerms
seller_category: str
documents: OnboardSubEntityDocuments
additional_info: AdditionalInfo
# v2.0 only — deprecated; a v3.0 sole trader is onboarded as a `company` with representatives
individual: Individual


class InstrumentDocument:
Expand Down
32 changes: 23 additions & 9 deletions checkout_sdk/accounts/accounts_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from checkout_sdk.accounts.accounts import (
EtagHeader, OnboardEntityRequest, UpdateScheduleRequest, AccountsPaymentInstrument,
PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest,
ReserveRuleRequest, EntityFileRequest, EntityRequirementUpdateRequest
ReserveRuleRequest, EntityFileRequest, EntityRequirementUpdateRequest, SchemaVersionHeader
)
from checkout_sdk.api_client import ApiClient
from checkout_sdk.authorization_type import AuthorizationType
Expand All @@ -14,6 +14,8 @@
from checkout_sdk.common.enums import Currency
from checkout_sdk.files.files import FileRequest

DEFAULT_SCHEMA_VERSION = '3.0'


class AccountsClient(Client):
__ACCOUNTS_PATH = 'accounts'
Expand All @@ -34,18 +36,26 @@ def __init__(self, api_client: ApiClient,
authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH)
self.__files_client = files_client

@staticmethod
def __build_schema_version_headers(schema_version: str):
headers = SchemaVersionHeader()
headers.accept = 'application/json;schema_version=' + schema_version
return headers

def upload_file(self, file_request: FileRequest):
return self.__files_client.submit_file(
self.__FILES_PATH,
self._sdk_authorization(),
file_request,
multipart_file='path')

def create_entity(self, onboard_entity_request: OnboardEntityRequest):
def create_entity(self, onboard_entity_request: OnboardEntityRequest,
schema_version: str = DEFAULT_SCHEMA_VERSION):
return self._api_client.post(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH),
self._sdk_authorization(),
onboard_entity_request)
onboard_entity_request,
headers=self.__build_schema_version_headers(schema_version))

def retrieve_payment_instrument_details(self, entity_id: str, payment_instrument_id: str):
return self._api_client.get(
Expand All @@ -54,16 +64,19 @@ def retrieve_payment_instrument_details(self, entity_id: str, payment_instrument
self._sdk_authorization()
)

def get_entity(self, entity_id: str):
def get_entity(self, entity_id: str, schema_version: str = DEFAULT_SCHEMA_VERSION):
return self._api_client.get(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id),
self._sdk_authorization())
self._sdk_authorization(),
headers=self.__build_schema_version_headers(schema_version))

def update_entity(self, entity_id: str, onboard_entity_request: OnboardEntityRequest):
def update_entity(self, entity_id: str, onboard_entity_request: OnboardEntityRequest,
schema_version: str = DEFAULT_SCHEMA_VERSION):
return self._api_client.put(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id),
self._sdk_authorization(),
onboard_entity_request)
onboard_entity_request,
headers=self.__build_schema_version_headers(schema_version))

def create_payment_instrument(self, entity_id: str, accounts_payment_instrument: AccountsPaymentInstrument):
warn('Deprecated use add_payment_instrument instead', DeprecationWarning,
Expand Down Expand Up @@ -149,10 +162,11 @@ def update_reserve_rule(self, entity_id: str, reserve_rule_id: str, etag: str, u
entity_id, self.__RESERVE_RULES_PATH, reserve_rule_id)
return self._api_client.put(path, self._sdk_authorization(), update_request, headers=headers)

def get_entity_requirements(self, entity_id: str):
def get_entity_requirements(self, entity_id: str, schema_version: str = DEFAULT_SCHEMA_VERSION):
return self._api_client.get(
self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__REQUIREMENTS_PATH),
self._sdk_authorization())
self._sdk_authorization(),
headers=self.__build_schema_version_headers(schema_version))

def get_entity_requirement_details(self, entity_id: str, requirement_id: str):
return self._api_client.get(
Expand Down
5 changes: 3 additions & 2 deletions checkout_sdk/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ def __init__(self, configuration: CheckoutConfiguration, base_uri: str):
def get(self,
path,
authorization: SdkAuthorization,
params=None):
return self.invoke(method='GET', path=path, authorization=authorization, params=params)
params=None,
headers=None):
return self.invoke(method='GET', path=path, authorization=authorization, params=params, headers=headers)

def post(self,
path,
Expand Down
36 changes: 36 additions & 0 deletions tests/accounts/accounts_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,39 @@ def test_should_resolve_entity_requirement(self, mocker, client: AccountsClient)

assert client.resolve_entity_requirement('entity_id', 'requirement_id', body) == 'response'
assert_api_call(mock, 'accounts/entities/entity_id/requirements/requirement_id', body)

# Accounts API v3.0 — schema_version negotiation via the Accept header

@staticmethod
def _accept_header(mock):
return mock.call_args.kwargs['headers'].accept

def test_should_create_entity_with_default_schema_version(self, mocker, client: AccountsClient):
mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response')

assert client.create_entity(OnboardEntityRequest()) == 'response'
assert self._accept_header(mock) == 'application/json;schema_version=3.0'

def test_should_get_entity_with_default_schema_version(self, mocker, client: AccountsClient):
mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response')

assert client.get_entity('entity_id') == 'response'
assert self._accept_header(mock) == 'application/json;schema_version=3.0'

def test_should_update_entity_with_default_schema_version(self, mocker, client: AccountsClient):
mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response')

assert client.update_entity('entity_id', OnboardEntityRequest()) == 'response'
assert self._accept_header(mock) == 'application/json;schema_version=3.0'

def test_should_get_entity_requirements_with_default_schema_version(self, mocker, client: AccountsClient):
mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response')

assert client.get_entity_requirements('entity_id') == 'response'
assert self._accept_header(mock) == 'application/json;schema_version=3.0'

def test_should_override_schema_version(self, mocker, client: AccountsClient):
mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response')

assert client.get_entity('entity_id', schema_version='2.0') == 'response'
assert self._accept_header(mock) == 'application/json;schema_version=2.0'
Loading
Loading