diff --git a/.gitignore b/.gitignore index a951e551..be5b848e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ venv/ dist build checkout_sdk.egg-info -htmlcov \ No newline at end of file +htmlcov +.cursor/rules/ +.cursor/skills/ +.vscode/settings.json diff --git a/checkout_sdk/accounts/accounts.py b/checkout_sdk/accounts/accounts.py index 82caf521..503bbd94 100644 --- a/checkout_sdk/accounts/accounts.py +++ b/checkout_sdk/accounts/accounts.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Dict from checkout_sdk.common.common import Phone, Address from checkout_sdk.common.common import ResidentialStatusType, AccountHolderIdentification @@ -382,3 +383,54 @@ class UpdateScheduleRequest: class PaymentInstrumentsQuery: status: str + + +class ReserveRuleType(str, Enum): + ROLLING = 'rolling' + + +class HoldingDuration: + weeks: int + + +class RollingReserveRule: + percentage: float + holding_duration: HoldingDuration + + +class ReserveRuleRequest: + type: ReserveRuleType + rolling: RollingReserveRule + valid_from: str + + +class FilePurpose(str, Enum): + ADDITIONAL_DOCUMENT = 'additional_document' + ARTICLES_OF_ASSOCIATION = 'articles_of_association' + BANK_VERIFICATION = 'bank_verification' + CERTIFIED_AUTHORISED_SIGNATORY = 'certified_authorised_signatory' + COMPANY_OWNERSHIP = 'company_ownership' + IDENTIFICATION = 'identification' + IDENTITY_VERIFICATION = 'identity_verification' + DISPUTE_EVIDENCE = 'dispute_evidence' + COMPANY_VERIFICATION = 'company_verification' + FINANCIAL_VERIFICATION = 'financial_verification' + TAX_VERIFICATION = 'tax_verification' + PROOF_OF_LEGALITY = 'proof_of_legality' + PROOF_OF_PRINCIPAL_ADDRESS = 'proof_of_principal_address' + SHAREHOLDER_STRUCTURE = 'shareholder_structure' + PROOF_OF_RESIDENTIAL_ADDRESS = 'proof_of_residential_address' + PROOF_OF_REGISTRATION = 'proof_of_registration' + + +class EntityFileRequest: + purpose: FilePurpose + + +class EtagHeader: + etag: str + + def get_header_mappings(self) -> Dict[str, str]: + return { + 'etag': 'If-Match' + } diff --git a/checkout_sdk/accounts/accounts_client.py b/checkout_sdk/accounts/accounts_client.py index 86b6bd84..5348fa3b 100644 --- a/checkout_sdk/accounts/accounts_client.py +++ b/checkout_sdk/accounts/accounts_client.py @@ -2,8 +2,11 @@ from warnings import warn -from checkout_sdk.accounts.accounts import OnboardEntityRequest, UpdateScheduleRequest, AccountsPaymentInstrument, \ - PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest +from checkout_sdk.accounts.accounts import ( + EtagHeader, OnboardEntityRequest, UpdateScheduleRequest, AccountsPaymentInstrument, + PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest, + ReserveRuleRequest, EntityFileRequest +) from checkout_sdk.api_client import ApiClient from checkout_sdk.authorization_type import AuthorizationType from checkout_sdk.checkout_configuration import CheckoutConfiguration @@ -19,6 +22,8 @@ class AccountsClient(Client): __FILES_PATH = 'files' __PAYOUT_SCHEDULES_PATH = 'payout-schedules' __PAYMENT_INSTRUMENTS_PATH = 'payment-instruments' + __MEMBERS_PATH = 'members' + __RESERVE_RULES_PATH = 'reserve-rules' def __init__(self, api_client: ApiClient, files_client: ApiClient, @@ -106,3 +111,50 @@ def update_payout_schedule(self, entity_id: str, currency: Currency, self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__PAYOUT_SCHEDULES_PATH), self._sdk_authorization(), {currency: update_schedule_request}) + + def get_sub_entity_members(self, entity_id: str): + return self._api_client.get( + self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__MEMBERS_PATH), + self._sdk_authorization()) + + def reinvite_sub_entity_member(self, entity_id: str, user_id: str): + return self._api_client.put( + self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__MEMBERS_PATH, user_id), + self._sdk_authorization()) + + def create_reserve_rule(self, entity_id: str, create_request: ReserveRuleRequest): + return self._api_client.post( + self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__RESERVE_RULES_PATH), + self._sdk_authorization(), + create_request) + + def get_reserve_rules(self, entity_id: str): + return self._api_client.get( + self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, entity_id, self.__RESERVE_RULES_PATH), + self._sdk_authorization()) + + def get_reserve_rule_details(self, entity_id: str, reserve_rule_id: str): + path = self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, + entity_id, self.__RESERVE_RULES_PATH, reserve_rule_id) + return self._api_client.get(path, self._sdk_authorization()) + + def update_reserve_rule(self, entity_id: str, reserve_rule_id: str, etag: str, update_request: ReserveRuleRequest): + headers = None + if (etag is not None): + headers = EtagHeader() + headers.etag = etag + + path = self.build_path(self.__ACCOUNTS_PATH, self.__ENTITIES_PATH, + entity_id, self.__RESERVE_RULES_PATH, reserve_rule_id) + return self._api_client.put(path, self._sdk_authorization(), update_request, headers=headers) + + def upload_entity_file(self, entity_id: str, entity_file_request: EntityFileRequest): + return self.__files_client.post( + self.build_path(self.__ENTITIES_PATH, entity_id, self.__FILES_PATH), + self._sdk_authorization(), + entity_file_request) + + def retrieve_entity_file(self, entity_id: str, file_id: str): + return self.__files_client.get( + self.build_path(self.__ENTITIES_PATH, entity_id, self.__FILES_PATH, file_id), + self._sdk_authorization()) diff --git a/checkout_sdk/agenticcommerce/__init__.py b/checkout_sdk/agenticcommerce/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/agenticcommerce/agentic_commerce.py b/checkout_sdk/agenticcommerce/agentic_commerce.py new file mode 100644 index 00000000..575dc656 --- /dev/null +++ b/checkout_sdk/agenticcommerce/agentic_commerce.py @@ -0,0 +1,92 @@ +from __future__ import absolute_import + +from enum import Enum +from typing import List, Dict +from datetime import datetime + +from checkout_sdk.common.enums import Country, Currency + + +class DelegatedPaymentMethodType(str, Enum): + CARD = 'card' + + +class DelegatedCardNumberType(str, Enum): + FPAN = 'fpan' + NETWORK_TOKEN = 'network_token' + + +class DelegatedPaymentAllowanceReason(str, Enum): + ONE_TIME = 'one_time' + + +class DelegatedCardFundingType(str, Enum): + CREDIT = 'credit' + DEBIT = 'debit' + PREPAID = 'prepaid' + + +class DelegatedPaymentMethodCard: + type: DelegatedPaymentMethodType + card_number_type: DelegatedCardNumberType + number: str + exp_month: str = None + exp_year: str = None + name: str = None + cvc: str = None + cryptogram: str = None + eci_value: str = None + checks_performed: List[str] = None + iin: str = None + display_card_funding_type: DelegatedCardFundingType = None + display_wallet_type: str = None + display_brand: str = None + display_last4: str = None + metadata: Dict[str, str] = None + + def __init__(self): + self.type = DelegatedPaymentMethodType.CARD + + +class DelegatedPaymentAllowance: + reason: DelegatedPaymentAllowanceReason + max_amount: int + currency: Currency + merchant_id: str + checkout_session_id: str + expires_at: datetime + + +class DelegatedPaymentBillingAddress: + name: str + line_one: str + line_two: str = None + city: str + state: str = None + postal_code: str + country: Country + + +class DelegatedPaymentRiskSignal: + type: str + score: int + action: str + + +class DelegatedPaymentRequest: + payment_method: DelegatedPaymentMethodCard + allowance: DelegatedPaymentAllowance + billing_address: DelegatedPaymentBillingAddress = None + risk_signals: List[DelegatedPaymentRiskSignal] + metadata: Dict[str, str] + + +class DelegatedPaymentHeaders: + signature: str + timestamp: str + api_version: str + + def get_header_mappings(self) -> Dict[str, str]: + return { + 'api_version': 'API-Version' + } diff --git a/checkout_sdk/agenticcommerce/agentic_commerce_client.py b/checkout_sdk/agenticcommerce/agentic_commerce_client.py new file mode 100644 index 00000000..d69dac1f --- /dev/null +++ b/checkout_sdk/agenticcommerce/agentic_commerce_client.py @@ -0,0 +1,25 @@ +from __future__ import absolute_import + +from checkout_sdk.agenticcommerce.agentic_commerce import DelegatedPaymentRequest, DelegatedPaymentHeaders +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client + + +class AgenticCommerceClient(Client): + __AGENTIC_COMMERCE_PATH = 'agentic_commerce' + __DELEGATE_PAYMENT_PATH = 'delegate_payment' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY) + + def create_delegated_payment_token(self, request: DelegatedPaymentRequest, headers: DelegatedPaymentHeaders): + return self._api_client.post( + self.build_path(self.__AGENTIC_COMMERCE_PATH, self.__DELEGATE_PAYMENT_PATH), + self._sdk_authorization(), + request, + headers=headers + ) diff --git a/checkout_sdk/api_client.py b/checkout_sdk/api_client.py index 34a6fb92..7da47125 100644 --- a/checkout_sdk/api_client.py +++ b/checkout_sdk/api_client.py @@ -43,21 +43,24 @@ def post(self, path, authorization: SdkAuthorization, request=None, - idempotency_key: str = None): + idempotency_key: str = None, + headers=None): return self.invoke(method='POST', path=path, authorization=authorization, body=request, - idempotency_key=idempotency_key) + idempotency_key=idempotency_key, headers=headers) def put(self, path, authorization: SdkAuthorization, - request=None): - return self.invoke(method='PUT', path=path, authorization=authorization, body=request) + request=None, + headers=None): + return self.invoke(method='PUT', path=path, authorization=authorization, body=request, headers=headers) def patch(self, path, authorization: SdkAuthorization, - request=None): - return self.invoke(method='PATCH', path=path, authorization=authorization, body=request) + request=None, + headers=None): + return self.invoke(method='PATCH', path=path, authorization=authorization, body=request, headers=headers) def delete(self, path, @@ -80,37 +83,33 @@ def invoke(self, idempotency_key: str = None, params=None, file_request: FileRequest = None, - multipart_file=None): + multipart_file=None, + headers=None): - headers = { + request_headers = { 'User-Agent': 'checkout-sdk-python/' + VERSION, 'Accept': 'application/json', 'Authorization': authorization.get_authorization_header(), 'Content-Type': 'application/json'} if idempotency_key is not None: - headers['Cko-Idempotency-Key'] = idempotency_key + request_headers['Cko-Idempotency-Key'] = idempotency_key + + if headers is not None: + custom_headers = self._process_custom_headers(headers) + request_headers.update(custom_headers) base_uri = self._base_uri + path try: - json_body = None - params_dict = None - files = None - - if body is not None: - json_body = json.dumps(body, cls=JsonSerializer) - elif params is not None: - params_dict = json.loads(json.dumps(params, cls=JsonSerializer)) - elif file_request is not None: - headers.pop('Content-Type') - files, json_body = get_file_request(file_request, multipart_file) + json_body, params_dict, files = self._prepare_request_payload( + request_headers, body, params, file_request, multipart_file) self._logger.info(method + ' ' + path) response = self._http_client.request(method=method, url=base_uri, - headers=headers, + headers=request_headers, params=params_dict, data=json_body, files=files) @@ -130,5 +129,40 @@ def invoke(self, else: contents = response.text return ResponseWrapper(http_metadata, contents) - else: - return ResponseWrapper(http_metadata) + return ResponseWrapper(http_metadata) + + def _prepare_request_payload(self, request_headers, body, params, file_request, multipart_file): + json_body = None + params_dict = None + files = None + if body is not None: + json_body = json.dumps(body, cls=JsonSerializer) + elif params is not None: + params_dict = json.loads(json.dumps(params, cls=JsonSerializer)) + elif file_request is not None: + request_headers.pop('Content-Type') + files, json_body = get_file_request(file_request, multipart_file) + return json_body, params_dict, files + + def _process_custom_headers(self, custom_headers): + if custom_headers is None: + return {} + + headers = {} + custom_mappings = {} + if hasattr(custom_headers, 'get_header_mappings'): + custom_mappings = custom_headers.get_header_mappings() + + for attr_name in dir(custom_headers): + if attr_name.startswith('_') or callable(getattr(custom_headers, attr_name)): + continue + + value = getattr(custom_headers, attr_name) + if value is not None and value != '': + header_name = custom_mappings.get(attr_name, self._convert_property_to_header(attr_name)) + headers[header_name] = str(value) + + return headers + + def _convert_property_to_header(self, property_name): + return '-'.join(word.capitalize() for word in property_name.split('_')) diff --git a/checkout_sdk/checkout_api.py b/checkout_sdk/checkout_api.py index 2d17b900..6fbc5552 100644 --- a/checkout_sdk/checkout_api.py +++ b/checkout_sdk/checkout_api.py @@ -4,6 +4,7 @@ from checkout_sdk.api_client import ApiClient from checkout_sdk.balances.balances_client import BalancesClient from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.compliancerequests.compliance_requests_client import ComplianceRequestsClient from checkout_sdk.customers.customers_client import CustomersClient from checkout_sdk.disputes.disputes_client import DisputesClient from checkout_sdk.financial.financial_client import FinancialClient @@ -25,6 +26,15 @@ from checkout_sdk.metadata.metadata_client import CardMetadataClient from checkout_sdk.forward.forward_client import ForwardClient from checkout_sdk.payments.setups.setups_client import PaymentSetupsClient +from checkout_sdk.agenticcommerce.agentic_commerce_client import AgenticCommerceClient +from checkout_sdk.payments.applepay.applepay_client import ApplePayClient +from checkout_sdk.payments.googlepay.googlepay_client import GooglePayClient +from checkout_sdk.standaloneaccountupdater.standalone_account_updater_client import StandaloneAccountUpdaterClient +from checkout_sdk.identities.amlscreening.amlscreening_client import AmlScreeningClient +from checkout_sdk.identities.faceauthentication.faceauthentication_client import FaceAuthenticationClient +from checkout_sdk.identities.iddocumentverification.iddocumentverification_client import IdDocumentVerificationClient +from checkout_sdk.identities.applicants.applicants_client import ApplicantsClient +from checkout_sdk.identities.identityverification.identityverification_client import IdentityVerificationClient def _base_api_client(configuration: CheckoutConfiguration) -> ApiClient: @@ -56,6 +66,7 @@ def __init__(self, configuration: CheckoutConfiguration): super().__init__(base_api_client, configuration) self.tokens = TokensClient(api_client=base_api_client, configuration=configuration) self.customers = CustomersClient(api_client=base_api_client, configuration=configuration) + self.compliance_requests = ComplianceRequestsClient(api_client=base_api_client, configuration=configuration) self.instruments = InstrumentsClient(api_client=base_api_client, configuration=configuration) self.payments = PaymentsClient(api_client=base_api_client, configuration=configuration) self.sessions = SessionsClient(api_client=base_api_client, configuration=configuration) @@ -78,3 +89,14 @@ def __init__(self, configuration: CheckoutConfiguration): self.payment_sessions = PaymentSessionsClient(api_client=base_api_client, configuration=configuration) self.forward = ForwardClient(api_client=base_api_client, configuration=configuration) self.setups = PaymentSetupsClient(api_client=base_api_client, configuration=configuration) + self.agentic_commerce = AgenticCommerceClient(api_client=base_api_client, configuration=configuration) + self.apple_pay = ApplePayClient(api_client=base_api_client, configuration=configuration) + self.google_pay = GooglePayClient(api_client=base_api_client, configuration=configuration) + self.standalone_account_updater = StandaloneAccountUpdaterClient(api_client=base_api_client, + configuration=configuration) + self.aml_screening = AmlScreeningClient(api_client=base_api_client, configuration=configuration) + self.face_authentication = FaceAuthenticationClient(api_client=base_api_client, configuration=configuration) + self.id_document_verification = IdDocumentVerificationClient(api_client=base_api_client, + configuration=configuration) + self.applicants = ApplicantsClient(api_client=base_api_client, configuration=configuration) + self.identity_verification = IdentityVerificationClient(api_client=base_api_client, configuration=configuration) diff --git a/checkout_sdk/compliancerequests/__init__.py b/checkout_sdk/compliancerequests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/compliancerequests/compliance_requests.py b/checkout_sdk/compliancerequests/compliance_requests.py new file mode 100644 index 00000000..85711f6e --- /dev/null +++ b/checkout_sdk/compliancerequests/compliance_requests.py @@ -0,0 +1,19 @@ +from __future__ import absolute_import + +from typing import List + + +class ComplianceRespondedField: + name: str + value: str + not_available: bool + + +class ComplianceRespondedFields: + sender: List[ComplianceRespondedField] + recipient: List[ComplianceRespondedField] + + +class ComplianceRequestRespondRequest: + fields: ComplianceRespondedFields + comments: str diff --git a/checkout_sdk/compliancerequests/compliance_requests_client.py b/checkout_sdk/compliancerequests/compliance_requests_client.py new file mode 100644 index 00000000..f54d7c5e --- /dev/null +++ b/checkout_sdk/compliancerequests/compliance_requests_client.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.compliancerequests.compliance_requests import ComplianceRequestRespondRequest + + +class ComplianceRequestsClient(Client): + __COMPLIANCE_REQUESTS_PATH = 'compliance-requests' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def get_compliance_request(self, payment_id: str): + return self._api_client.get( + self.build_path(self.__COMPLIANCE_REQUESTS_PATH, payment_id), + self._sdk_authorization()) + + def respond_to_compliance_request(self, payment_id: str, request: ComplianceRequestRespondRequest): + return self._api_client.post( + self.build_path(self.__COMPLIANCE_REQUESTS_PATH, payment_id), + self._sdk_authorization(), request) diff --git a/checkout_sdk/forward/forward.py b/checkout_sdk/forward/forward.py index d6bb831f..aba285ea 100644 --- a/checkout_sdk/forward/forward.py +++ b/checkout_sdk/forward/forward.py @@ -85,3 +85,14 @@ class ForwardRequest: reference: str = None processing_channel_id: str = None network_token: NetworkToken = None + + +class SecretRequest: + name: str + value: str + entity_id: str = None + + +class UpdateSecretRequest: + value: str + entity_id: str = None diff --git a/checkout_sdk/forward/forward_client.py b/checkout_sdk/forward/forward_client.py index 1b4fb8e9..f22fabde 100644 --- a/checkout_sdk/forward/forward_client.py +++ b/checkout_sdk/forward/forward_client.py @@ -2,11 +2,12 @@ from checkout_sdk.authorization_type import AuthorizationType from checkout_sdk.checkout_configuration import CheckoutConfiguration from checkout_sdk.client import Client -from checkout_sdk.forward.forward import ForwardRequest +from checkout_sdk.forward.forward import ForwardRequest, SecretRequest, UpdateSecretRequest class ForwardClient(Client): __FORWARD_PATH = 'forward' + __SECRETS_PATH = 'secrets' def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): super().__init__(api_client=api_client, @@ -18,3 +19,29 @@ def forward_request(self, request: ForwardRequest): def get(self, request_id: str): return self._api_client.get(self.build_path(self.__FORWARD_PATH, request_id), self._sdk_authorization()) + + def create_secret(self, request: SecretRequest): + return self._api_client.post( + self.build_path(self.__FORWARD_PATH, self.__SECRETS_PATH), + self._sdk_authorization(), + request + ) + + def list_secrets(self): + return self._api_client.get( + self.build_path(self.__FORWARD_PATH, self.__SECRETS_PATH), + self._sdk_authorization() + ) + + def update_secret(self, name: str, request: UpdateSecretRequest): + return self._api_client.patch( + self.build_path(self.__FORWARD_PATH, self.__SECRETS_PATH, name), + self._sdk_authorization(), + request + ) + + def delete_secret(self, name: str): + return self._api_client.delete( + self.build_path(self.__FORWARD_PATH, self.__SECRETS_PATH, name), + self._sdk_authorization() + ) diff --git a/checkout_sdk/identities/amlscreening/__init__.py b/checkout_sdk/identities/amlscreening/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/identities/amlscreening/amlscreening.py b/checkout_sdk/identities/amlscreening/amlscreening.py new file mode 100644 index 00000000..c80830d3 --- /dev/null +++ b/checkout_sdk/identities/amlscreening/amlscreening.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + + +class SearchParameters: + configuration_identifier: str + + +class AmlScreeningRequest: + applicant_id: str + search_parameters: SearchParameters + monitored: bool diff --git a/checkout_sdk/identities/amlscreening/amlscreening_client.py b/checkout_sdk/identities/amlscreening/amlscreening_client.py new file mode 100644 index 00000000..6a4c06db --- /dev/null +++ b/checkout_sdk/identities/amlscreening/amlscreening_client.py @@ -0,0 +1,25 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.identities.amlscreening.amlscreening import AmlScreeningRequest + + +class AmlScreeningClient(Client): + __AML_PATH = 'aml-verifications' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def create_aml_screening(self, request: AmlScreeningRequest): + return self._api_client.post(self.__AML_PATH, + self._sdk_authorization(), + request) + + def get_aml_screening(self, aml_verification_id: str): + return self._api_client.get(self.build_path(self.__AML_PATH, aml_verification_id), + self._sdk_authorization()) diff --git a/checkout_sdk/identities/applicants/__init__.py b/checkout_sdk/identities/applicants/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/identities/applicants/applicants.py b/checkout_sdk/identities/applicants/applicants.py new file mode 100644 index 00000000..8730a866 --- /dev/null +++ b/checkout_sdk/identities/applicants/applicants.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import + + +class CreateApplicantRequest: + external_applicant_id: str + email: str + external_applicant_name: str + + +class UpdateApplicantRequest: + email: str + external_applicant_name: str diff --git a/checkout_sdk/identities/applicants/applicants_client.py b/checkout_sdk/identities/applicants/applicants_client.py new file mode 100644 index 00000000..087964bf --- /dev/null +++ b/checkout_sdk/identities/applicants/applicants_client.py @@ -0,0 +1,36 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.identities.applicants.applicants import CreateApplicantRequest, UpdateApplicantRequest + + +class ApplicantsClient(Client): + __APPLICANTS_PATH = 'applicants' + __ANONYMIZE_PATH = 'anonymize' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def create_applicant(self, request: CreateApplicantRequest): + return self._api_client.post(self.__APPLICANTS_PATH, + self._sdk_authorization(), + request) + + def get_applicant(self, applicant_id: str): + return self._api_client.get(self.build_path(self.__APPLICANTS_PATH, applicant_id), + self._sdk_authorization()) + + def update_applicant(self, applicant_id: str, request: UpdateApplicantRequest): + return self._api_client.patch(self.build_path(self.__APPLICANTS_PATH, applicant_id), + self._sdk_authorization(), + request) + + def anonymize_applicant(self, applicant_id: str): + return self._api_client.post( + self.build_path(self.__APPLICANTS_PATH, applicant_id, self.__ANONYMIZE_PATH), + self._sdk_authorization()) diff --git a/checkout_sdk/identities/faceauthentication/__init__.py b/checkout_sdk/identities/faceauthentication/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/identities/faceauthentication/faceauthentication.py b/checkout_sdk/identities/faceauthentication/faceauthentication.py new file mode 100644 index 00000000..8252414b --- /dev/null +++ b/checkout_sdk/identities/faceauthentication/faceauthentication.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import + + +class ClientInformation: + pre_selected_residence_country: str + pre_selected_language: str + + +class FaceAuthenticationRequest: + applicant_id: str + user_journey_id: str + + +class FaceAuthenticationAttemptRequest: + redirect_url: str + client_information: ClientInformation diff --git a/checkout_sdk/identities/faceauthentication/faceauthentication_client.py b/checkout_sdk/identities/faceauthentication/faceauthentication_client.py new file mode 100644 index 00000000..37cb96cb --- /dev/null +++ b/checkout_sdk/identities/faceauthentication/faceauthentication_client.py @@ -0,0 +1,51 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.identities.faceauthentication.faceauthentication import ( + FaceAuthenticationRequest, FaceAuthenticationAttemptRequest +) + + +class FaceAuthenticationClient(Client): + __FACE_AUTHENTICATIONS_PATH = 'face-authentications' + __ANONYMIZE_PATH = 'anonymize' + __ATTEMPTS_PATH = 'attempts' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def create_face_authentication(self, request: FaceAuthenticationRequest): + return self._api_client.post(self.__FACE_AUTHENTICATIONS_PATH, + self._sdk_authorization(), + request) + + def get_face_authentication(self, face_authentication_id: str): + return self._api_client.get(self.build_path(self.__FACE_AUTHENTICATIONS_PATH, face_authentication_id), + self._sdk_authorization()) + + def anonymize_face_authentication(self, face_authentication_id: str): + return self._api_client.post( + self.build_path(self.__FACE_AUTHENTICATIONS_PATH, face_authentication_id, self.__ANONYMIZE_PATH), + self._sdk_authorization()) + + def create_face_authentication_attempt(self, face_authentication_id: str, request: FaceAuthenticationAttemptRequest): + return self._api_client.post( + self.build_path(self.__FACE_AUTHENTICATIONS_PATH, face_authentication_id, self.__ATTEMPTS_PATH), + self._sdk_authorization(), + request) + + def get_face_authentication_attempts(self, face_authentication_id: str): + return self._api_client.get( + self.build_path(self.__FACE_AUTHENTICATIONS_PATH, face_authentication_id, self.__ATTEMPTS_PATH), + self._sdk_authorization()) + + def get_face_authentication_attempt(self, face_authentication_id: str, attempt_id: str): + return self._api_client.get( + self.build_path(self.__FACE_AUTHENTICATIONS_PATH, face_authentication_id, self.__ATTEMPTS_PATH, + attempt_id), + self._sdk_authorization()) diff --git a/checkout_sdk/identities/iddocumentverification/__init__.py b/checkout_sdk/identities/iddocumentverification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/identities/iddocumentverification/iddocumentverification.py b/checkout_sdk/identities/iddocumentverification/iddocumentverification.py new file mode 100644 index 00000000..3cebf3cd --- /dev/null +++ b/checkout_sdk/identities/iddocumentverification/iddocumentverification.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import + + +class DeclaredData: + name: str + + +class IdDocumentVerificationRequest: + applicant_id: str + user_journey_id: str + declared_data: DeclaredData + + +class IdDocumentVerificationAttemptRequest: + document_front: str + document_back: str diff --git a/checkout_sdk/identities/iddocumentverification/iddocumentverification_client.py b/checkout_sdk/identities/iddocumentverification/iddocumentverification_client.py new file mode 100644 index 00000000..e873caec --- /dev/null +++ b/checkout_sdk/identities/iddocumentverification/iddocumentverification_client.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.identities.iddocumentverification.iddocumentverification import ( + IdDocumentVerificationRequest, IdDocumentVerificationAttemptRequest +) + + +class IdDocumentVerificationClient(Client): + __ID_DOCUMENT_VERIFICATIONS_PATH = 'id-document-verifications' + __ANONYMIZE_PATH = 'anonymize' + __ATTEMPTS_PATH = 'attempts' + __PDF_REPORT_PATH = 'pdf-report' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def create_id_document_verification(self, request: IdDocumentVerificationRequest): + return self._api_client.post(self.__ID_DOCUMENT_VERIFICATIONS_PATH, + self._sdk_authorization(), + request) + + def get_id_document_verification(self, id_document_verification_id: str): + return self._api_client.get( + self.build_path(self.__ID_DOCUMENT_VERIFICATIONS_PATH, id_document_verification_id), + self._sdk_authorization()) + + def anonymize_id_document_verification(self, id_document_verification_id: str): + return self._api_client.post( + self.build_path(self.__ID_DOCUMENT_VERIFICATIONS_PATH, id_document_verification_id, + self.__ANONYMIZE_PATH), + self._sdk_authorization()) + + def create_id_document_verification_attempt(self, id_document_verification_id: str, + request: IdDocumentVerificationAttemptRequest): + return self._api_client.post( + self.build_path(self.__ID_DOCUMENT_VERIFICATIONS_PATH, id_document_verification_id, + self.__ATTEMPTS_PATH), + self._sdk_authorization(), + request) + + def get_id_document_verification_attempts(self, id_document_verification_id: str): + return self._api_client.get( + self.build_path(self.__ID_DOCUMENT_VERIFICATIONS_PATH, id_document_verification_id, + self.__ATTEMPTS_PATH), + self._sdk_authorization()) + + def get_id_document_verification_attempt(self, id_document_verification_id: str, attempt_id: str): + return self._api_client.get( + self.build_path(self.__ID_DOCUMENT_VERIFICATIONS_PATH, id_document_verification_id, + self.__ATTEMPTS_PATH, attempt_id), + self._sdk_authorization()) + + def get_id_document_verification_report(self, id_document_verification_id: str): + return self._api_client.get( + self.build_path(self.__ID_DOCUMENT_VERIFICATIONS_PATH, id_document_verification_id, + self.__PDF_REPORT_PATH), + self._sdk_authorization()) diff --git a/checkout_sdk/identities/identityverification/__init__.py b/checkout_sdk/identities/identityverification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/identities/identityverification/identityverification.py b/checkout_sdk/identities/identityverification/identityverification.py new file mode 100644 index 00000000..cb62341e --- /dev/null +++ b/checkout_sdk/identities/identityverification/identityverification.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import + + +class DeclaredData: + name: str + + +class ClientInformation: + pre_selected_residence_country: str + pre_selected_language: str + + +class IdentityVerificationRequest: + applicant_id: str + declared_data: DeclaredData + user_journey_id: str + + +class IdentityVerificationAndAttemptRequest: + declared_data: DeclaredData + redirect_url: str + user_journey_id: str + applicant_id: str + + +class IdentityVerificationAttemptRequest: + redirect_url: str + client_information: ClientInformation diff --git a/checkout_sdk/identities/identityverification/identityverification_client.py b/checkout_sdk/identities/identityverification/identityverification_client.py new file mode 100644 index 00000000..24ac2d5e --- /dev/null +++ b/checkout_sdk/identities/identityverification/identityverification_client.py @@ -0,0 +1,66 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.identities.identityverification.identityverification import ( + IdentityVerificationRequest, + IdentityVerificationAndAttemptRequest, + IdentityVerificationAttemptRequest, +) + + +class IdentityVerificationClient(Client): + __CREATE_AND_OPEN_PATH = 'create-and-open-idv' + __IDENTITY_VERIFICATIONS_PATH = 'identity-verifications' + __ANONYMIZE_PATH = 'anonymize' + __ATTEMPTS_PATH = 'attempts' + __PDF_REPORT_PATH = 'pdf-report' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def create_identity_verification_and_attempt(self, request: IdentityVerificationAndAttemptRequest): + return self._api_client.post(self.__CREATE_AND_OPEN_PATH, + self._sdk_authorization(), + request) + + def create_identity_verification(self, request: IdentityVerificationRequest): + return self._api_client.post(self.__IDENTITY_VERIFICATIONS_PATH, + self._sdk_authorization(), + request) + + def get_identity_verification(self, identity_verification_id: str): + return self._api_client.get(self.build_path(self.__IDENTITY_VERIFICATIONS_PATH, identity_verification_id), + self._sdk_authorization()) + + def anonymize_identity_verification(self, identity_verification_id: str): + return self._api_client.post( + self.build_path(self.__IDENTITY_VERIFICATIONS_PATH, identity_verification_id, self.__ANONYMIZE_PATH), + self._sdk_authorization()) + + def create_identity_verification_attempt(self, identity_verification_id: str, + request: IdentityVerificationAttemptRequest): + return self._api_client.post( + self.build_path(self.__IDENTITY_VERIFICATIONS_PATH, identity_verification_id, self.__ATTEMPTS_PATH), + self._sdk_authorization(), + request) + + def get_identity_verification_attempts(self, identity_verification_id: str): + return self._api_client.get( + self.build_path(self.__IDENTITY_VERIFICATIONS_PATH, identity_verification_id, self.__ATTEMPTS_PATH), + self._sdk_authorization()) + + def get_identity_verification_attempt(self, identity_verification_id: str, attempt_id: str): + return self._api_client.get( + self.build_path(self.__IDENTITY_VERIFICATIONS_PATH, identity_verification_id, self.__ATTEMPTS_PATH, + attempt_id), + self._sdk_authorization()) + + def get_identity_verification_report(self, identity_verification_id: str): + return self._api_client.get( + self.build_path(self.__IDENTITY_VERIFICATIONS_PATH, identity_verification_id, self.__PDF_REPORT_PATH), + self._sdk_authorization()) diff --git a/checkout_sdk/oauth_scopes.py b/checkout_sdk/oauth_scopes.py index d2d91ed8..b1583534 100644 --- a/checkout_sdk/oauth_scopes.py +++ b/checkout_sdk/oauth_scopes.py @@ -12,6 +12,7 @@ class OAuthScopes(str, Enum): VAULT_APME_ENROLLMENT = 'vault:apme-enrollment' VAULT_CARD_METADATA = 'vault:card-metadata' VAULT_NETWORK_TOKENS = 'vault:network-tokens' + VAULT_GPAYME_ENROLLMENT = 'vault:gpayme-enrollment' GATEWAY = 'gateway' GATEWAY_PAYMENT = 'gateway:payment' GATEWAY_PAYMENT_DETAILS = 'gateway:payment-details' diff --git a/checkout_sdk/payments/applepay/__init__.py b/checkout_sdk/payments/applepay/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/payments/applepay/applepay.py b/checkout_sdk/payments/applepay/applepay.py new file mode 100644 index 00000000..16688b8b --- /dev/null +++ b/checkout_sdk/payments/applepay/applepay.py @@ -0,0 +1,18 @@ +from enum import Enum + + +class ProtocolVersions(str, Enum): + EC_V1 = 'ec_v1' + RSA_V1 = 'rsa_v1' + + +class UploadCertificateRequest: + content: str + + +class EnrollDomainRequest: + domain: str + + +class GenerateSigningRequestRequest: + protocol_version: ProtocolVersions diff --git a/checkout_sdk/payments/applepay/applepay_client.py b/checkout_sdk/payments/applepay/applepay_client.py new file mode 100644 index 00000000..c6b5e304 --- /dev/null +++ b/checkout_sdk/payments/applepay/applepay_client.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.payments.applepay.applepay import UploadCertificateRequest, EnrollDomainRequest, \ + GenerateSigningRequestRequest + + +class ApplePayClient(Client): + __CERTIFICATES_PATH = 'applepay/certificates' + __ENROLLMENTS_PATH = 'applepay/enrollments' + __SIGNING_REQUESTS_PATH = 'applepay/signing-requests' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def upload_payment_processing_certificate(self, request: UploadCertificateRequest): + return self._api_client.post(self.__CERTIFICATES_PATH, + self._sdk_authorization(AuthorizationType.PUBLIC_KEY), request) + + def enroll_domain(self, request: EnrollDomainRequest): + return self._api_client.post(self.__ENROLLMENTS_PATH, + self._sdk_authorization(AuthorizationType.OAUTH), + request) + + def generate_certificate_signing_request(self, request: GenerateSigningRequestRequest): + return self._api_client.post(self.__SIGNING_REQUESTS_PATH, + self._sdk_authorization(AuthorizationType.PUBLIC_KEY), request) diff --git a/checkout_sdk/payments/googlepay/__init__.py b/checkout_sdk/payments/googlepay/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/payments/googlepay/googlepay.py b/checkout_sdk/payments/googlepay/googlepay.py new file mode 100644 index 00000000..857911de --- /dev/null +++ b/checkout_sdk/payments/googlepay/googlepay.py @@ -0,0 +1,8 @@ +class GooglePayEnrollmentRequest: + entity_id: str + email_address: str + accept_terms_of_service: bool + + +class GooglePayRegisterDomainRequest: + web_domain: str diff --git a/checkout_sdk/payments/googlepay/googlepay_client.py b/checkout_sdk/payments/googlepay/googlepay_client.py new file mode 100644 index 00000000..d62ae90d --- /dev/null +++ b/checkout_sdk/payments/googlepay/googlepay_client.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.payments.googlepay.googlepay import GooglePayEnrollmentRequest, GooglePayRegisterDomainRequest + + +class GooglePayClient(Client): + __GOOGLEPAY_ENROLLMENTS_PATH = 'googlepay/enrollments' + __DOMAIN_PATH = 'domain' + __DOMAINS_PATH = 'domains' + __STATE_PATH = 'state' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.OAUTH) + + def create_enrollment(self, request: GooglePayEnrollmentRequest): + return self._api_client.post(self.__GOOGLEPAY_ENROLLMENTS_PATH, self._sdk_authorization(), request) + + def register_domain(self, entity_id: str, request: GooglePayRegisterDomainRequest): + return self._api_client.post( + self.build_path(self.__GOOGLEPAY_ENROLLMENTS_PATH, entity_id, self.__DOMAIN_PATH), + self._sdk_authorization(), + request) + + def get_registered_domains(self, entity_id: str): + return self._api_client.get( + self.build_path(self.__GOOGLEPAY_ENROLLMENTS_PATH, entity_id, self.__DOMAINS_PATH), + self._sdk_authorization()) + + def get_enrollment_state(self, entity_id: str): + return self._api_client.get( + self.build_path(self.__GOOGLEPAY_ENROLLMENTS_PATH, entity_id, self.__STATE_PATH), + self._sdk_authorization()) diff --git a/checkout_sdk/standaloneaccountupdater/__init__.py b/checkout_sdk/standaloneaccountupdater/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/standaloneaccountupdater/standalone_account_updater.py b/checkout_sdk/standaloneaccountupdater/standalone_account_updater.py new file mode 100644 index 00000000..379fba59 --- /dev/null +++ b/checkout_sdk/standaloneaccountupdater/standalone_account_updater.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import + + +class CardDetailsRequest: + number: str + expiry_month: int + expiry_year: int + + +class InstrumentReference: + id: str + + +class SourceOptions: + card: CardDetailsRequest + instrument: InstrumentReference + + +class GetUpdatedCardCredentialsRequest: + source_options: SourceOptions diff --git a/checkout_sdk/standaloneaccountupdater/standalone_account_updater_client.py b/checkout_sdk/standaloneaccountupdater/standalone_account_updater_client.py new file mode 100644 index 00000000..74845675 --- /dev/null +++ b/checkout_sdk/standaloneaccountupdater/standalone_account_updater_client.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.standaloneaccountupdater.standalone_account_updater import GetUpdatedCardCredentialsRequest + + +class StandaloneAccountUpdaterClient(Client): + __ACCOUNT_UPDATER_PATH = 'account-updater/cards' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.OAUTH) + + def get_updated_card_credentials(self, request: GetUpdatedCardCredentialsRequest): + return self._api_client.post(self.__ACCOUNT_UPDATER_PATH, + self._sdk_authorization(), + request) diff --git a/tests/accounts/accounts_client_test.py b/tests/accounts/accounts_client_test.py index adaf7769..85a0ab65 100644 --- a/tests/accounts/accounts_client_test.py +++ b/tests/accounts/accounts_client_test.py @@ -1,7 +1,8 @@ import pytest from checkout_sdk.accounts.accounts import OnboardEntityRequest, AccountsPaymentInstrument, UpdateScheduleRequest, \ - PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest + PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest, ReserveRuleRequest, \ + EntityFileRequest, FilePurpose from checkout_sdk.accounts.accounts_client import AccountsClient from checkout_sdk.common.enums import Currency from checkout_sdk.files.files import FileRequest @@ -60,3 +61,37 @@ def test_should_update_payout_schedule(self, mocker, client: AccountsClient): def test_should_retrieve_payout_schedule(self, mocker, client: AccountsClient): mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') assert client.retrieve_payout_schedule('entity_id') == 'response' + + def test_should_get_sub_entity_members(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_sub_entity_members('entity_id') == 'response' + + def test_should_reinvite_sub_entity_member(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + assert client.reinvite_sub_entity_member('entity_id', 'user_id') == 'response' + + def test_should_create_reserve_rule(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_reserve_rule('entity_id', ReserveRuleRequest()) == 'response' + + def test_should_get_reserve_rules(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_reserve_rules('entity_id') == 'response' + + def test_should_get_reserve_rule_details(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_reserve_rule_details('entity_id', 'reserve_rule_id') == 'response' + + def test_should_update_reserve_rule(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + assert client.update_reserve_rule('entity_id', 'reserve_rule_id', 'etag_value', ReserveRuleRequest()) == 'response' + + def test_should_upload_entity_file(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + request = EntityFileRequest() + request.purpose = FilePurpose.IDENTIFICATION + assert client.upload_entity_file('entity_id', request) == 'response' + + def test_should_retrieve_entity_file(self, mocker, client: AccountsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_entity_file('entity_id', 'file_id') == 'response' diff --git a/tests/accounts/accounts_integration_test.py b/tests/accounts/accounts_integration_test.py index 9c1d623f..fb846ed9 100644 --- a/tests/accounts/accounts_integration_test.py +++ b/tests/accounts/accounts_integration_test.py @@ -1,14 +1,15 @@ from __future__ import absolute_import import os +from datetime import datetime, timedelta, timezone import pytest from checkout_sdk import CheckoutSdk from checkout_sdk.accounts.accounts import OnboardEntityRequest, ContactDetails, Profile, Individual, \ DateOfBirth, Identification, EntityEmailAddresses, Company, EntityRepresentative, PaymentInstrumentRequest, \ - InstrumentDocument, InstrumentDetailsFasterPayments -from checkout_sdk.checkout_api import CheckoutApi + InstrumentDocument, InstrumentDetailsFasterPayments, ReserveRuleRequest, RollingReserveRule, \ + HoldingDuration, EntityFileRequest, FilePurpose from checkout_sdk.common.enums import Currency, Country, InstrumentType from checkout_sdk.files.files import FileRequest from checkout_sdk.oauth_scopes import OAuthScopes @@ -26,16 +27,7 @@ def accounts_checkout_api(): .build() -def upload_file(oauth_api: CheckoutApi): - request = FileRequest() - request.file = os.path.join(get_project_root(), 'tests', 'resources', 'checkout.jpeg') - request.purpose = 'bank_verification' - response = oauth_api.accounts.upload_file(request) - assert_response(response, 'id', '_links') - return response - - -def test_should_create_get_and_update_onboard_entity(oauth_api): +def test_should_create_get_and_update_onboard_entity(accounts_checkout_api): onboard_entity_request = OnboardEntityRequest() onboard_entity_request.reference = new_uuid()[:14] email_addresses = EntityEmailAddresses() @@ -59,11 +51,11 @@ def test_should_create_get_and_update_onboard_entity(oauth_api): onboard_entity_request.individual.identification = Identification() onboard_entity_request.individual.identification.national_id_number = 'AB123456C' - create_entity_response = oauth_api.accounts.create_entity(onboard_entity_request) + create_entity_response = accounts_checkout_api.accounts.create_entity(onboard_entity_request) assert_response(create_entity_response, 'id', 'reference') - get_entity_response = oauth_api.accounts.get_entity(create_entity_response.id) + get_entity_response = accounts_checkout_api.accounts.get_entity(create_entity_response.id) assert_response(get_entity_response, 'id', @@ -80,15 +72,15 @@ def test_should_create_get_and_update_onboard_entity(oauth_api): onboard_entity_request.individual.first_name = 'John' - update_response = oauth_api.accounts.update_entity(create_entity_response.id, onboard_entity_request) + update_response = accounts_checkout_api.accounts.update_entity(create_entity_response.id, onboard_entity_request) assert_response(update_response, 'id') assert create_entity_response.id == update_response.id -def test_should_upload_file(oauth_api): - upload_file(oauth_api) +def test_should_upload_file(accounts_checkout_api): + upload_file(accounts_checkout_api) def test_should_create_and_retrieve_payment_instrument(accounts_checkout_api): @@ -150,3 +142,182 @@ def test_should_create_and_retrieve_payment_instrument(accounts_checkout_api): query_response = accounts_checkout_api.accounts.query_payment_instruments(entity_response.id) assert_response(query_response, 'data') + + +def test_should_get_sub_entity_members(accounts_checkout_api): + entity_id = create_test_entity(accounts_checkout_api) + + members_response = accounts_checkout_api.accounts.get_sub_entity_members(entity_id) + + assert members_response is not None + + +def test_create_reserve_rule_should_return_valid_response(accounts_checkout_api): + entity_id = create_test_entity(accounts_checkout_api) + reserve_rule_request = create_valid_reserve_rule_request() + + response = accounts_checkout_api.accounts.create_reserve_rule(entity_id, reserve_rule_request) + + validate_reserve_rule_id_response(response) + + +def test_get_reserve_rules_should_return_valid_response(accounts_checkout_api): + entity_id = create_test_entity(accounts_checkout_api) + reserve_rule_request = create_valid_reserve_rule_request() + create_response = accounts_checkout_api.accounts.create_reserve_rule(entity_id, reserve_rule_request) + validate_reserve_rule_id_response(create_response) + + response = accounts_checkout_api.accounts.get_reserve_rules(entity_id) + + validate_reserve_rules_response(response) + + +def test_get_reserve_rule_details_should_return_valid_response(accounts_checkout_api): + entity_id = create_test_entity(accounts_checkout_api) + reserve_rule_request = create_valid_reserve_rule_request() + create_response = accounts_checkout_api.accounts.create_reserve_rule(entity_id, reserve_rule_request) + validate_reserve_rule_id_response(create_response) + + response = accounts_checkout_api.accounts.get_reserve_rule_details(entity_id, create_response.id) + + validate_reserve_rule_response(response, reserve_rule_request) + + +def test_update_reserve_rule_should_return_valid_response(accounts_checkout_api): + entity_id = create_test_entity(accounts_checkout_api) + original_request = create_valid_reserve_rule_request() + create_response = accounts_checkout_api.accounts.create_reserve_rule(entity_id, original_request) + validate_reserve_rule_id_response(create_response) + + update_request = create_valid_reserve_rule_request() + update_request.rolling.percentage = 15.0 + update_request.rolling.holding_duration.weeks = 16 + + etag = None + if hasattr(create_response, 'http_metadata') and hasattr(create_response.http_metadata, 'headers'): + headers = create_response.http_metadata.headers + if 'etag' in headers: + etag = headers['etag'] + elif 'ETag' in headers: + etag = headers['ETag'] + + response = accounts_checkout_api.accounts.update_reserve_rule( + entity_id, + create_response.id, + etag, + update_request + ) + + validate_reserve_rule_id_response(response) + assert response.id == create_response.id + + +def test_should_upload_entity_file_and_retrieve(accounts_checkout_api): + entity_id = create_test_entity(accounts_checkout_api) + + request = EntityFileRequest() + request.purpose = FilePurpose.IDENTIFICATION + + upload_response = accounts_checkout_api.accounts.upload_entity_file(entity_id, request) + + assert_response(upload_response, 'id', '_links') + assert upload_response.id is not None + assert upload_response.id != '' + + file_id = upload_response.id + retrieve_response = accounts_checkout_api.accounts.retrieve_entity_file(entity_id, file_id) + + assert_response(retrieve_response, 'id') + assert retrieve_response.id == file_id + + +# Common methods +def upload_file(api): + request = FileRequest() + request.file = os.path.join(get_project_root(), 'tests', 'resources', 'checkout.jpeg') + request.purpose = 'bank_verification' + response = api.accounts.upload_file(request) + assert_response(response, 'id', '_links') + return response + + +def create_test_entity(api): + entity_request = OnboardEntityRequest() + entity_request.reference = new_uuid()[:15] + entity_request.contact_details = build_contact_details() + entity_request.profile = build_profile() + entity_request.company = Company() + entity_request.company.business_registration_number = '01234567' + entity_request.company.legal_name = 'Reserve Rules Test Inc.' + entity_request.company.trading_name = 'Reserve Rules Test' + entity_request.company.principal_address = address() + entity_request.company.registered_address = address() + representative = EntityRepresentative() + representative.first_name = 'John' + representative.last_name = 'Doe' + representative.address = address() + entity_request.company.representatives = [representative] + + entity_response = api.accounts.create_entity(entity_request) + assert_response(entity_response, 'id') + + return entity_response.id + + +def build_contact_details(): + contact_details = ContactDetails() + contact_details.phone = phone() + contact_details.email_addresses = EntityEmailAddresses() + contact_details.email_addresses.primary = random_email() + return contact_details + + +def build_profile(): + profile = Profile() + profile.urls = ['https://www.superheroexample.com'] + profile.mccs = ['0742'] + return profile + + +def create_valid_reserve_rule_request(): + holding_duration = HoldingDuration() + holding_duration.weeks = 8 + + rolling_rule = RollingReserveRule() + rolling_rule.percentage = 12.5 + rolling_rule.holding_duration = holding_duration + + reserve_rule_request = ReserveRuleRequest() + reserve_rule_request.type = 'rolling' + reserve_rule_request.rolling = rolling_rule + reserve_rule_request.valid_from = (datetime.now(timezone.utc) + timedelta(days=30)).isoformat() + + return reserve_rule_request + + +def validate_reserve_rule_id_response(response): + assert response is not None + assert_response(response, 'id') + assert response.id is not None + assert response.id != '' + + +def validate_reserve_rules_response(response): + assert response is not None + assert_response(response, 'data') + assert response.data is not None + assert len(response.data) > 0 + assert response.data[0].id is not None + assert hasattr(response.data[0], 'type') + + +def validate_reserve_rule_response(response, original_request): + assert response is not None + assert_response(response, 'id', 'type', 'rolling') + assert response.id is not None + assert response.type == original_request.type + assert response.rolling is not None + assert response.rolling.percentage == original_request.rolling.percentage + assert response.rolling.holding_duration is not None + assert response.rolling.holding_duration.weeks == original_request.rolling.holding_duration.weeks + assert hasattr(response, 'valid_from') diff --git a/tests/agenticcommerce/agentic_commerce_client_test.py b/tests/agenticcommerce/agentic_commerce_client_test.py new file mode 100644 index 00000000..d7da2083 --- /dev/null +++ b/tests/agenticcommerce/agentic_commerce_client_test.py @@ -0,0 +1,54 @@ +import pytest + +from checkout_sdk.agenticcommerce.agentic_commerce_client import AgenticCommerceClient +from checkout_sdk.agenticcommerce.agentic_commerce import DelegatedPaymentRequest, DelegatedPaymentHeaders + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return AgenticCommerceClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +class TestAgenticCommerceClient: + + def test_create_delegated_payment_token(self, mocker, client: AgenticCommerceClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + request = DelegatedPaymentRequest() + headers = DelegatedPaymentHeaders() + + response = client.create_delegated_payment_token(request, headers) + + assert response == 'response' + + def test_create_delegated_payment_token_with_none_request(self, mocker, client: AgenticCommerceClient): + mock_post = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + headers = DelegatedPaymentHeaders() + + response = client.create_delegated_payment_token(None, headers) + + assert response == 'response' + mock_post.assert_called_once() + args = mock_post.call_args[0] + assert args[2] is None + + def test_create_delegated_payment_token_with_none_headers(self, mocker, client: AgenticCommerceClient): + mock_post = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + request = DelegatedPaymentRequest() + + response = client.create_delegated_payment_token(request, None) + + assert response == 'response' + mock_post.assert_called_once() + kwargs = mock_post.call_args.kwargs + assert kwargs['headers'] is None + + def test_create_delegated_payment_token_calls_correct_endpoint(self, mocker, client: AgenticCommerceClient): + mock_post = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + request = DelegatedPaymentRequest() + headers = DelegatedPaymentHeaders() + + client.create_delegated_payment_token(request, headers) + + mock_post.assert_called_once() + args = mock_post.call_args[0] + assert 'agentic_commerce/delegate_payment' in args[0] diff --git a/tests/agenticcommerce/agentic_commerce_integration_test.py b/tests/agenticcommerce/agentic_commerce_integration_test.py new file mode 100644 index 00000000..1f4c2d3b --- /dev/null +++ b/tests/agenticcommerce/agentic_commerce_integration_test.py @@ -0,0 +1,169 @@ +from __future__ import absolute_import + +import pytest + +from datetime import datetime, timezone, timedelta +from checkout_sdk.agenticcommerce.agentic_commerce import ( + DelegatedPaymentRequest, DelegatedPaymentHeaders, DelegatedPaymentMethodCard, + DelegatedPaymentAllowance, DelegatedPaymentBillingAddress, DelegatedPaymentRiskSignal, + DelegatedCardNumberType, DelegatedPaymentAllowanceReason +) +from checkout_sdk.common.enums import Currency, Country +from checkout_sdk.exception import CheckoutApiException +from tests.checkout_test_utils import assert_response + + +@pytest.mark.skip(reason="Requires a valid HMAC signing key and merchant enabled for agentic commerce") +def test_should_create_delegated_payment_token(default_api): + request = build_valid_delegated_payment_request() + headers = build_valid_delegated_payment_headers() + + response = default_api.agentic_commerce.create_delegated_payment_token(request, headers) + + assert_delegated_payment_token_response(response) + assert_response(response, + 'id', + 'created', + 'metadata') + + +@pytest.mark.skip(reason="Requires a valid HMAC signing key and merchant enabled for agentic commerce") +def test_should_create_delegated_payment_token_with_billing_address(default_api): + request = build_valid_delegated_payment_request_with_billing_address() + headers = build_valid_delegated_payment_headers() + + response = default_api.agentic_commerce.create_delegated_payment_token(request, headers) + + assert_delegated_payment_token_response(response) + assert_response(response, + 'id', + 'created', + 'metadata') + + +@pytest.mark.skip(reason="Requires a valid HMAC signing key and merchant enabled for agentic commerce") +def test_should_create_delegated_payment_token_with_network_token(default_api): + request = build_valid_delegated_payment_request_with_network_token() + headers = build_valid_delegated_payment_headers() + + response = default_api.agentic_commerce.create_delegated_payment_token(request, headers) + + assert_delegated_payment_token_response(response) + assert_response(response, + 'id', + 'created', + 'metadata') + + +def test_should_fail_create_delegated_payment_token_with_invalid_request(default_api): + from checkout_sdk.agenticcommerce.agentic_commerce import DelegatedPaymentRequest + invalid_request = DelegatedPaymentRequest() + headers = build_valid_delegated_payment_headers() + + with pytest.raises(CheckoutApiException) as exc_info: + default_api.agentic_commerce.create_delegated_payment_token(invalid_request, headers) + + assert exc_info.value.http_metadata.status_code in [400, 422] + + +def test_should_fail_create_delegated_payment_token_with_invalid_signature(default_api): + request = build_valid_delegated_payment_request() + + from checkout_sdk.agenticcommerce.agentic_commerce import DelegatedPaymentHeaders + invalid_headers = DelegatedPaymentHeaders() + invalid_headers.signature = "invalid-signature" + invalid_headers.timestamp = "2026-03-11T10:30:00Z" + + with pytest.raises(CheckoutApiException) as exc_info: + default_api.agentic_commerce.create_delegated_payment_token(request, invalid_headers) + + assert exc_info.value.http_metadata.status_code in [401, 403] + + +# Common methods +def build_valid_delegated_payment_request(): + payment_method = DelegatedPaymentMethodCard() + payment_method.card_number_type = DelegatedCardNumberType.FPAN + payment_method.number = "4242424242424242" + payment_method.exp_month = "11" + payment_method.exp_year = "2026" + payment_method.metadata = {"issuing_bank": "test"} + + allowance = DelegatedPaymentAllowance() + allowance.reason = DelegatedPaymentAllowanceReason.ONE_TIME + allowance.max_amount = 10000 + allowance.currency = Currency.USD + allowance.merchant_id = "cli_vkuhvk4vjn2edkps7dfsq6emqm" + allowance.checkout_session_id = "1PQrsT" + allowance.expires_at = datetime.now(timezone.utc) + timedelta(hours=1) + + risk_signal = DelegatedPaymentRiskSignal() + risk_signal.type = "card_testing" + risk_signal.score = 10 + risk_signal.action = "blocked" + + request = DelegatedPaymentRequest() + request.payment_method = payment_method + request.allowance = allowance + request.risk_signals = [risk_signal] + request.metadata = {"campaign": "q4"} + + return request + + +def build_valid_delegated_payment_request_with_billing_address(): + request = build_valid_delegated_payment_request() + + billing_address = DelegatedPaymentBillingAddress() + billing_address.name = "John Doe" + billing_address.line_one = "123 Test Street" + billing_address.city = "London" + billing_address.postal_code = "SW1A 1AA" + billing_address.country = Country.GB + + request.billing_address = billing_address + + return request + + +def build_valid_delegated_payment_request_with_network_token(): + request = build_valid_delegated_payment_request() + + request.payment_method.card_number_type = DelegatedCardNumberType.NETWORK_TOKEN + request.payment_method.number = "4111111111111111" + + return request + + +def build_valid_delegated_payment_headers(): + headers = DelegatedPaymentHeaders() + headers.signature = "eyJtZX..." + headers.timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + headers.api_version = "2026-01-01" + return headers + + +def build_delegated_payment_response_mock(): + return { + 'id': 'vt_abc123def456ghi789', + 'created': '2026-03-11T10:30:00Z', + 'metadata': {'psp': 'checkout.com'} + } + + +def assert_delegated_payment_response(response): + assert_response(response, + 'id', + 'created', + 'metadata') + + +def assert_delegated_payment_token_response(response): + assert response is not None + assert hasattr(response, 'id') + assert response['id'] is not None + assert response['id'].startswith('vt_') + assert hasattr(response, 'created') + assert response['created'] is not None + assert hasattr(response, 'metadata') + assert response['metadata'] is not None diff --git a/tests/compliancerequests/compliance_requests_client_test.py b/tests/compliancerequests/compliance_requests_client_test.py new file mode 100644 index 00000000..b00d2ca7 --- /dev/null +++ b/tests/compliancerequests/compliance_requests_client_test.py @@ -0,0 +1,57 @@ +import pytest + +from checkout_sdk.compliancerequests.compliance_requests_client import ComplianceRequestsClient +from checkout_sdk.compliancerequests.compliance_requests import ComplianceRequestRespondRequest + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return ComplianceRequestsClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +class TestComplianceRequestsClient: + + def test_get_compliance_request(self, mocker, client: ComplianceRequestsClient): + mock_get = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + + response = client.get_compliance_request(payment_id) + + assert response == 'response' + mock_get.assert_called_once() + args = mock_get.call_args[0] + assert f'compliance-requests/{payment_id}' in args[0] + + def test_get_compliance_request_with_none_payment_id(self, mocker, client: ComplianceRequestsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + with pytest.raises(TypeError, match="sequence item 1: expected str"): + client.get_compliance_request(None) + + def test_respond_to_compliance_request(self, mocker, client: ComplianceRequestsClient): + mock_post = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + request = ComplianceRequestRespondRequest() + + response = client.respond_to_compliance_request(payment_id, request) + + assert response == 'response' + mock_post.assert_called_once() + args = mock_post.call_args[0] + assert f'compliance-requests/{payment_id}' in args[0] + assert args[2] == request + + def test_respond_to_compliance_request_with_none_payment_id(self, mocker, client: ComplianceRequestsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + request = ComplianceRequestRespondRequest() + + with pytest.raises(TypeError, match="sequence item 1: expected str"): + client.respond_to_compliance_request(None, request) + + def test_respond_to_compliance_request_with_none_request(self, mocker, client: ComplianceRequestsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + + response = client.respond_to_compliance_request(payment_id, None) + + assert response == 'response' diff --git a/tests/compliancerequests/compliance_requests_integration_test.py b/tests/compliancerequests/compliance_requests_integration_test.py new file mode 100644 index 00000000..1862051e --- /dev/null +++ b/tests/compliancerequests/compliance_requests_integration_test.py @@ -0,0 +1,153 @@ +from __future__ import absolute_import + +import pytest + +from checkout_sdk.exception import CheckoutApiException +from checkout_sdk.compliancerequests.compliance_requests import ( + ComplianceRequestRespondRequest, + ComplianceRespondedFields, + ComplianceRespondedField +) +from tests.checkout_test_utils import assert_response + + +@pytest.mark.skip(reason="Requires a payment ID associated with an active compliance request") +def test_should_get_compliance_request(default_api): + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + + response = default_api.compliance_requests.get_compliance_request(payment_id) + + assert_compliance_request_response(response) + assert_response(response, + 'payment_id', + 'status', + 'amount', + 'currency') + assert response['payment_id'] == payment_id + + +@pytest.mark.skip(reason="Requires a payment ID associated with an active compliance request") +def test_should_respond_to_compliance_request(default_api): + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + request = build_valid_respond_request() + + response = default_api.compliance_requests.respond_to_compliance_request(payment_id, request) + + assert_compliance_respond_response(response) + + +@pytest.mark.skip(reason="Requires a payment ID associated with an active compliance request") +def test_should_respond_to_compliance_request_with_not_available_fields(default_api): + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + request = build_valid_respond_request_with_not_available_field() + + response = default_api.compliance_requests.respond_to_compliance_request(payment_id, request) + + assert_compliance_respond_response(response) + + +def test_should_fail_get_compliance_request_with_invalid_payment_id(default_api): + invalid_payment_id = "pay_invalid_payment_id" + + with pytest.raises(CheckoutApiException) as exc_info: + default_api.compliance_requests.get_compliance_request(invalid_payment_id) + + assert exc_info.value.http_metadata.status_code in [401, 404, 422] + + +def test_should_fail_respond_to_compliance_request_with_invalid_payment_id(default_api): + invalid_payment_id = "pay_invalid_payment_id" + request = build_valid_respond_request() + + with pytest.raises(CheckoutApiException) as exc_info: + default_api.compliance_requests.respond_to_compliance_request(invalid_payment_id, request) + + assert exc_info.value.http_metadata.status_code in [401, 404, 422] + + +def test_should_fail_respond_to_compliance_request_with_empty_request(default_api): + payment_id = "pay_fun26akvvjjerahhctaq2uzhu4" + from checkout_sdk.compliancerequests.compliance_requests import ComplianceRequestRespondRequest + empty_request = ComplianceRequestRespondRequest() + + with pytest.raises(CheckoutApiException) as exc_info: + default_api.compliance_requests.respond_to_compliance_request(payment_id, empty_request) + + assert exc_info.value.http_metadata.status_code in [400, 401, 422] + + +# Common methods +def build_valid_respond_request(): + sender_field = ComplianceRespondedField() + sender_field.name = "date_of_birth" + sender_field.value = "2000-01-01" + sender_field.not_available = False + + recipient_field = ComplianceRespondedField() + recipient_field.name = "full_name" + recipient_field.value = "John Doe" + recipient_field.not_available = False + + responded_fields = ComplianceRespondedFields() + responded_fields.sender = [sender_field] + responded_fields.recipient = [recipient_field] + + request = ComplianceRequestRespondRequest() + request.fields = responded_fields + request.comments = "Providing the requested compliance information" + + return request + + +def build_valid_respond_request_with_not_available_field(): + sender_field = ComplianceRespondedField() + sender_field.name = "social_security_number" + sender_field.value = None + sender_field.not_available = True + + responded_fields = ComplianceRespondedFields() + responded_fields.sender = [sender_field] + responded_fields.recipient = [] + + request = ComplianceRequestRespondRequest() + request.fields = responded_fields + request.comments = "Some fields are not available for compliance reasons" + + return request + + +def build_get_compliance_request_response_mock(): + return { + 'payment_id': 'pay_fun26akvvjjerahhctaq2uzhu4', + 'status': 'pending', + 'amount': '38.23', + 'currency': 'HKD', + 'created_at': '2026-03-11T10:30:00Z', + 'request_id': 'req_abc123def456', + 'fields_required': { + 'sender': ['date_of_birth', 'full_name'], + 'recipient': ['full_name', 'address'] + } + } + + +def build_respond_to_compliance_request_response_mock(): + return { + 'http_metadata': { + 'status_code': 204 + } + } + + +def assert_compliance_request_response(response): + assert_response(response, + 'payment_id', + 'status', + 'amount', + 'currency') + + +def assert_compliance_respond_response(response): + assert response is not None + if hasattr(response, 'http_metadata'): + assert hasattr(response, 'http_metadata') diff --git a/tests/conftest.py b/tests/conftest.py index f851c878..4405a7b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,7 +48,8 @@ def oauth_api(): .scopes([OAuthScopes.GATEWAY, OAuthScopes.VAULT, OAuthScopes.PAYOUTS_BANK_DETAILS, OAuthScopes.SESSIONS_APP, OAuthScopes.SESSIONS_BROWSER, OAuthScopes.FX, OAuthScopes.ACCOUNTS, OAuthScopes.FILES, OAuthScopes.TRANSFERS, OAuthScopes.BALANCES_VIEW, - OAuthScopes.VAULT_CARD_METADATA, OAuthScopes.FINANCIAL_ACTIONS]) \ + OAuthScopes.VAULT_CARD_METADATA, OAuthScopes.FINANCIAL_ACTIONS, + OAuthScopes.VAULT_REAL_TIME_ACCOUNT_UPDATER]) \ .build() diff --git a/tests/forward/forward_client_test.py b/tests/forward/forward_client_test.py index 868ef3e4..b2673440 100644 --- a/tests/forward/forward_client_test.py +++ b/tests/forward/forward_client_test.py @@ -1,6 +1,6 @@ import pytest -from checkout_sdk.forward.forward import ForwardRequest +from checkout_sdk.forward.forward import ForwardRequest, SecretRequest from checkout_sdk.forward.forward_client import ForwardClient @@ -9,7 +9,7 @@ def client(mock_sdk_configuration, mock_api_client): return ForwardClient(api_client=mock_api_client, configuration=mock_sdk_configuration) -class TestForexClient: +class TestForwardClient: def test_should_forward_request(self, mocker, client: ForwardClient): mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') @@ -18,3 +18,19 @@ def test_should_forward_request(self, mocker, client: ForwardClient): def test_should_get_forward_request(self, mocker, client: ForwardClient): mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') assert client.get('forward_id') == 'response' + + def test_should_create_secret(self, mocker, client: ForwardClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_secret(SecretRequest()) == 'response' + + def test_should_list_secrets(self, mocker, client: ForwardClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.list_secrets() == 'response' + + def test_should_update_secret(self, mocker, client: ForwardClient): + mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + assert client.update_secret('secret_name', SecretRequest()) == 'response' + + def test_should_delete_secret(self, mocker, client: ForwardClient): + mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.delete_secret('secret_name') == 'response' diff --git a/tests/forward/forward_integration_test.py b/tests/forward/forward_integration_test.py index 4349b0ba..269ee8a5 100644 --- a/tests/forward/forward_integration_test.py +++ b/tests/forward/forward_integration_test.py @@ -1,35 +1,36 @@ from __future__ import absolute_import import pytest +import uuid from checkout_sdk.forward.forward import ForwardRequest, IdSource, NetworkToken, MethodType, DestinationRequest, \ - Headers, DlocalSignature, DlocalParameters + Headers, DlocalSignature, DlocalParameters, SecretRequest from tests.checkout_test_utils import assert_response @pytest.mark.skip(reason='This test requires a valid id or Token source') def test_should_forward_and_get_request(default_api): - id_source = IdSource + id_source = IdSource() id_source.id = 'src_v5rgkf3gdtpuzjqesyxmyodnya' - network_token = NetworkToken + network_token = NetworkToken() network_token.enabled = True network_token.request_cryptogram = False - headers = Headers + headers = Headers() headers.encrypted = '' headers.raw = { 'Idempotency-Key': 'xe4fad12367dfgrds', 'Content-Type': 'application/json', } - dlocal_parameters = DlocalParameters + dlocal_parameters = DlocalParameters() dlocal_parameters.secret_key = '9f439fe1a9f96e67b047d3c1a28c33a2e' - signature = DlocalSignature + signature = DlocalSignature() signature.dlocal_parameters = dlocal_parameters - destination_request = DestinationRequest + destination_request = DestinationRequest() destination_request.url = 'https://example.com/forward' destination_request.method = MethodType.POST destination_request.headers = headers @@ -41,7 +42,7 @@ def test_should_forward_and_get_request(default_api): '"merchant_initiated": true}') destination_request.signature = signature - forward_request = ForwardRequest + forward_request = ForwardRequest() forward_request.source = id_source forward_request.reference = 'ORD-5023-4E89' forward_request.processing_channel_id = 'pc_azsiyswl7bwe2ynjzujy7lcjca' @@ -53,3 +54,54 @@ def test_should_forward_and_get_request(default_api): get_response = default_api.forward.get_forward_request(response['request_id']) assert_response(get_response, 'request_id', 'destination_request', 'destination_response') + + +def build_create_secret_request(name: str = None, value: str = "secret_value", entity_id: str = None) -> SecretRequest: + request = SecretRequest() + request.name = name or f"secret_{str(uuid.uuid4()).replace('-', '')[:16]}" + request.value = value + request.entity_id = entity_id + return request + + +def build_update_secret_request(value: str = "updated_value", entity_id: str = None) -> SecretRequest: + request = SecretRequest() + request.value = value + request.entity_id = entity_id + return request + + +def assert_secret_response(response, expected_name: str = None): + assert_response(response, 'name', 'created_at', 'updated_at', 'version') + if expected_name: + assert response['name'] == expected_name + + +@pytest.mark.skip(reason='This test requires forward secrets scopes and valid credentials') +def test_should_create_list_update_delete_secret(default_api): + create_request = build_create_secret_request(value="initial_value") + secret_name = create_request.name + + create_response = default_api.forward.create_secret(create_request) + assert_secret_response(create_response, secret_name) + + list_response = default_api.forward.list_secrets() + assert_response(list_response, 'data') + assert any(secret['name'] == secret_name for secret in list_response['data']) + + update_request = build_update_secret_request(value="new_updated_value") + update_response = default_api.forward.update_secret(secret_name, update_request) + assert_secret_response(update_response, secret_name) + + delete_response = default_api.forward.delete_secret(secret_name) + assert delete_response is not None + + +@pytest.mark.skip(reason='This test requires forward secrets scopes and valid credentials') +def test_should_create_secret_with_entity_id(default_api): + create_request = build_create_secret_request(entity_id="ent_test123") + + create_response = default_api.forward.create_secret(create_request) + assert_secret_response(create_response, create_request.name) + + default_api.forward.delete_secret(create_request.name) diff --git a/tests/identities/amlscreening/__init__.py b/tests/identities/amlscreening/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/identities/amlscreening/amlscreening_client_test.py b/tests/identities/amlscreening/amlscreening_client_test.py new file mode 100644 index 00000000..c6f0a4a2 --- /dev/null +++ b/tests/identities/amlscreening/amlscreening_client_test.py @@ -0,0 +1,20 @@ +import pytest + +from checkout_sdk.identities.amlscreening.amlscreening import AmlScreeningRequest +from checkout_sdk.identities.amlscreening.amlscreening_client import AmlScreeningClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return AmlScreeningClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +class TestAmlScreeningClient: + + def test_should_create_aml_screening(self, mocker, client: AmlScreeningClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_aml_screening(AmlScreeningRequest()) == 'response' + + def test_should_get_aml_screening(self, mocker, client: AmlScreeningClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_aml_screening('scr_7hr7swleu6guzjqesyxmyodnya') == 'response' diff --git a/tests/identities/amlscreening/amlscreening_integration_test.py b/tests/identities/amlscreening/amlscreening_integration_test.py new file mode 100644 index 00000000..e5622ef8 --- /dev/null +++ b/tests/identities/amlscreening/amlscreening_integration_test.py @@ -0,0 +1,59 @@ +import os + +import pytest + +from checkout_sdk.identities.amlscreening.amlscreening import AmlScreeningRequest, SearchParameters +from tests.checkout_test_utils import assert_response + +# tests + + +@pytest.mark.skip(reason='Requires valid applicant ID and AML configuration') +def test_should_create_aml_screening(default_api): + response = default_api.aml_screening.create_aml_screening(aml_screening_request()) + assert_aml_screening_response(response) + + +@pytest.mark.skip(reason='Requires valid applicant ID and AML configuration') +def test_should_get_aml_screening(default_api): + created = default_api.aml_screening.create_aml_screening(aml_screening_request()) + retrieved = default_api.aml_screening.get_aml_screening(created.id) + assert_aml_screening_response(retrieved) + + +@pytest.mark.skip(reason='Requires valid applicant ID and AML configuration') +def test_should_create_and_track_aml_screening_workflow(default_api): + created = default_api.aml_screening.create_aml_screening(aml_screening_request()) + assert_aml_screening_response(created) + + updated = default_api.aml_screening.get_aml_screening(created.id) + assert_aml_screening_response(updated) + assert updated.id == created.id + assert updated.applicant_id == created.applicant_id + + +@pytest.mark.skip(reason='Requires valid applicant ID and AML configuration') +def test_should_validate_monitoring_configuration(default_api): + request = aml_screening_request() + request.monitored = False + + response = default_api.aml_screening.create_aml_screening(request) + assert_aml_screening_response(response) + assert response.monitored is False + +# common functions + + +def aml_screening_request() -> AmlScreeningRequest: + search_parameters = SearchParameters() + search_parameters.configuration_identifier = os.environ.get('CHECKOUT_TEST_AML_CONFIG_ID', 'config_test_id') + + request = AmlScreeningRequest() + request.applicant_id = os.environ.get('CHECKOUT_TEST_APPLICANT_ID', 'aplt_test_applicant_id') + request.search_parameters = search_parameters + request.monitored = True + return request + + +def assert_aml_screening_response(response): + assert_response(response, 'http_metadata', 'id', 'applicant_id', 'status', 'search_parameters') diff --git a/tests/identities/applicants/__init__.py b/tests/identities/applicants/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/identities/applicants/applicants_client_test.py b/tests/identities/applicants/applicants_client_test.py new file mode 100644 index 00000000..4ad6a2fa --- /dev/null +++ b/tests/identities/applicants/applicants_client_test.py @@ -0,0 +1,30 @@ +import pytest + +from checkout_sdk.identities.applicants.applicants import CreateApplicantRequest, UpdateApplicantRequest +from checkout_sdk.identities.applicants.applicants_client import ApplicantsClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return ApplicantsClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +# tests + +class TestApplicantsClient: + + def test_should_create_applicant(self, mocker, client: ApplicantsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_applicant(CreateApplicantRequest()) == 'response' + + def test_should_get_applicant(self, mocker, client: ApplicantsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_applicant('aplt_7hr7swleu6guzjqesyxmyodnya') == 'response' + + def test_should_update_applicant(self, mocker, client: ApplicantsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + assert client.update_applicant('aplt_7hr7swleu6guzjqesyxmyodnya', UpdateApplicantRequest()) == 'response' + + def test_should_anonymize_applicant(self, mocker, client: ApplicantsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_applicant('aplt_7hr7swleu6guzjqesyxmyodnya') == 'response' diff --git a/tests/identities/applicants/applicants_integration_test.py b/tests/identities/applicants/applicants_integration_test.py new file mode 100644 index 00000000..6ebc3ecf --- /dev/null +++ b/tests/identities/applicants/applicants_integration_test.py @@ -0,0 +1,90 @@ +import pytest + +from checkout_sdk.exception import CheckoutApiException +from checkout_sdk.identities.applicants.applicants import CreateApplicantRequest, UpdateApplicantRequest +from tests.checkout_test_utils import assert_response, random_email + + +# tests + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_applicant(default_api): + response = default_api.applicants.create_applicant(create_applicant_request()) + assert_applicant_response(response) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_applicant(default_api): + created = default_api.applicants.create_applicant(create_applicant_request()) + retrieved = default_api.applicants.get_applicant(created.id) + assert_applicant_response(retrieved) + assert retrieved.id == created.id + assert retrieved.email == created.email + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_update_applicant(default_api): + created = default_api.applicants.create_applicant(create_applicant_request()) + request = update_applicant_request() + updated = default_api.applicants.update_applicant(created.id, request) + assert_applicant_response(updated) + assert updated.id == created.id + assert updated.email == request.email + assert updated.external_applicant_name == request.external_applicant_name + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_anonymize_applicant(default_api): + created = default_api.applicants.create_applicant(create_applicant_request()) + response = default_api.applicants.anonymize_applicant(created.id) + assert_response(response, 'http_metadata', 'id') + assert response.id == created.id + with pytest.raises(CheckoutApiException): + default_api.applicants.get_applicant(created.id) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_update_and_retrieve_applicant_workflow(default_api): + created = default_api.applicants.create_applicant(create_applicant_request()) + assert_applicant_response(created) + + request = update_applicant_request() + updated = default_api.applicants.update_applicant(created.id, request) + assert_applicant_response(updated) + assert updated.id == created.id + + retrieved = default_api.applicants.get_applicant(created.id) + assert_applicant_response(retrieved) + assert retrieved.id == created.id + assert retrieved.email == request.email + assert retrieved.external_applicant_name == request.external_applicant_name + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_validate_optional_fields(default_api): + request = CreateApplicantRequest() + request.email = random_email() + response = default_api.applicants.create_applicant(request) + assert_applicant_response(response) + assert response.email == request.email + + +# common methods + +def create_applicant_request() -> CreateApplicantRequest: + request = CreateApplicantRequest() + request.external_applicant_id = 'ext_test_applicant' + request.email = random_email() + request.external_applicant_name = 'Test Applicant Name' + return request + + +def update_applicant_request() -> UpdateApplicantRequest: + request = UpdateApplicantRequest() + request.email = random_email() + request.external_applicant_name = 'Updated Test Applicant Name' + return request + + +def assert_applicant_response(response): + assert_response(response, 'http_metadata', 'id', 'email') diff --git a/tests/identities/faceauthentication/__init__.py b/tests/identities/faceauthentication/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/identities/faceauthentication/faceauthentication_client_test.py b/tests/identities/faceauthentication/faceauthentication_client_test.py new file mode 100644 index 00000000..5e41eade --- /dev/null +++ b/tests/identities/faceauthentication/faceauthentication_client_test.py @@ -0,0 +1,42 @@ +import pytest + +from checkout_sdk.identities.faceauthentication.faceauthentication import ( + FaceAuthenticationRequest, FaceAuthenticationAttemptRequest +) +from checkout_sdk.identities.faceauthentication.faceauthentication_client import FaceAuthenticationClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return FaceAuthenticationClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +# tests + +class TestFaceAuthenticationClient: + + def test_should_create_face_authentication(self, mocker, client: FaceAuthenticationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_face_authentication(FaceAuthenticationRequest()) == 'response' + + def test_should_get_face_authentication(self, mocker, client: FaceAuthenticationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_face_authentication('fav_mtta050yudd54y5iqb5ijh8jtvz') == 'response' + + def test_should_anonymize_face_authentication(self, mocker, client: FaceAuthenticationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_face_authentication('fav_mtta050yudd54y5iqb5ijh8jtvz') == 'response' + + def test_should_create_face_authentication_attempt(self, mocker, client: FaceAuthenticationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_face_authentication_attempt('fav_mtta050yudd54y5iqb5ijh8jtvz', + FaceAuthenticationAttemptRequest()) == 'response' + + def test_should_get_face_authentication_attempts(self, mocker, client: FaceAuthenticationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_face_authentication_attempts('fav_mtta050yudd54y5iqb5ijh8jtvz') == 'response' + + def test_should_get_face_authentication_attempt(self, mocker, client: FaceAuthenticationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_face_authentication_attempt('fav_mtta050yudd54y5iqb5ijh8jtvz', + 'fatp_nk1wbmmczqumwt95k3v39mhbh2w') == 'response' diff --git a/tests/identities/faceauthentication/faceauthentication_integration_test.py b/tests/identities/faceauthentication/faceauthentication_integration_test.py new file mode 100644 index 00000000..fa067727 --- /dev/null +++ b/tests/identities/faceauthentication/faceauthentication_integration_test.py @@ -0,0 +1,109 @@ +import pytest + +from checkout_sdk.identities.faceauthentication.faceauthentication import ( + FaceAuthenticationRequest, FaceAuthenticationAttemptRequest, ClientInformation +) +from tests.checkout_test_utils import assert_response, new_uuid + + +# tests + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_face_authentication(default_api): + response = default_api.face_authentication.create_face_authentication(face_authentication_request()) + assert_face_authentication_response(response) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_face_authentication(default_api): + created = default_api.face_authentication.create_face_authentication(face_authentication_request()) + retrieved = default_api.face_authentication.get_face_authentication(created.id) + assert_face_authentication_response(retrieved) + assert retrieved.id == created.id + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_anonymize_face_authentication(default_api): + created = default_api.face_authentication.create_face_authentication(face_authentication_request()) + response = default_api.face_authentication.anonymize_face_authentication(created.id) + assert_response(response, 'http_metadata', 'id') + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_face_authentication_attempt(default_api): + created = default_api.face_authentication.create_face_authentication(face_authentication_request()) + attempt = default_api.face_authentication.create_face_authentication_attempt(created.id, + face_authentication_attempt_request()) + assert_face_authentication_attempt_response(attempt) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_face_authentication_attempts(default_api): + created = default_api.face_authentication.create_face_authentication(face_authentication_request()) + created_attempt = default_api.face_authentication.create_face_authentication_attempt( + created.id, face_authentication_attempt_request()) + attempts = default_api.face_authentication.get_face_authentication_attempts(created.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + assert any(a.id == created_attempt.id for a in attempts.data) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_face_authentication_attempt(default_api): + created = default_api.face_authentication.create_face_authentication(face_authentication_request()) + created_attempt = default_api.face_authentication.create_face_authentication_attempt( + created.id, face_authentication_attempt_request()) + retrieved = default_api.face_authentication.get_face_authentication_attempt(created.id, created_attempt.id) + assert_face_authentication_attempt_response(retrieved) + assert retrieved.id == created_attempt.id + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_perform_face_authentication_workflow(default_api): + created = default_api.face_authentication.create_face_authentication(face_authentication_request()) + assert_face_authentication_response(created) + + retrieved = default_api.face_authentication.get_face_authentication(created.id) + assert_face_authentication_response(retrieved) + assert retrieved.id == created.id + + attempt_request = face_authentication_attempt_request() + created_attempt = default_api.face_authentication.create_face_authentication_attempt(created.id, attempt_request) + assert_face_authentication_attempt_response(created_attempt) + + attempts = default_api.face_authentication.get_face_authentication_attempts(created.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + + retrieved_attempt = default_api.face_authentication.get_face_authentication_attempt(created.id, created_attempt.id) + assert_face_authentication_attempt_response(retrieved_attempt) + assert retrieved_attempt.id == created_attempt.id + + anonymized = default_api.face_authentication.anonymize_face_authentication(created.id) + assert_response(anonymized, 'http_metadata', 'id') + + +# common methods + +def face_authentication_request() -> FaceAuthenticationRequest: + request = FaceAuthenticationRequest() + request.applicant_id = new_uuid() + request.user_journey_id = new_uuid() + return request + + +def face_authentication_attempt_request() -> FaceAuthenticationAttemptRequest: + client_information = ClientInformation() + client_information.pre_selected_residence_country = 'US' + client_information.pre_selected_language = 'en-US' + + request = FaceAuthenticationAttemptRequest() + request.redirect_url = 'https://example.com/redirect' + request.client_information = client_information + return request + + +def assert_face_authentication_response(response): + assert_response(response, 'http_metadata', 'id', 'applicant_id', 'status') + + +def assert_face_authentication_attempt_response(response): + assert_response(response, 'http_metadata', 'id', 'status') diff --git a/tests/identities/iddocumentverification/__init__.py b/tests/identities/iddocumentverification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/identities/iddocumentverification/iddocumentverification_client_test.py b/tests/identities/iddocumentverification/iddocumentverification_client_test.py new file mode 100644 index 00000000..9aa60fe2 --- /dev/null +++ b/tests/identities/iddocumentverification/iddocumentverification_client_test.py @@ -0,0 +1,45 @@ +import pytest + +from checkout_sdk.identities.iddocumentverification.iddocumentverification import ( + IdDocumentVerificationRequest, IdDocumentVerificationAttemptRequest +) +from checkout_sdk.identities.iddocumentverification.iddocumentverification_client import IdDocumentVerificationClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return IdDocumentVerificationClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +# tests + +class TestIdDocumentVerificationClient: + + def test_should_create_id_document_verification(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_id_document_verification(IdDocumentVerificationRequest()) == 'response' + + def test_should_get_id_document_verification(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification('iddoc_12345') == 'response' + + def test_should_anonymize_id_document_verification(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_id_document_verification('iddoc_12345') == 'response' + + def test_should_create_id_document_verification_attempt(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_id_document_verification_attempt( + 'iddoc_12345', IdDocumentVerificationAttemptRequest()) == 'response' + + def test_should_get_id_document_verification_attempts(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification_attempts('iddoc_12345') == 'response' + + def test_should_get_id_document_verification_attempt(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification_attempt('iddoc_12345', 'attempt_67890') == 'response' + + def test_should_get_id_document_verification_report(self, mocker, client: IdDocumentVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification_report('iddoc_12345') == 'response' diff --git a/tests/identities/iddocumentverification/iddocumentverification_integration_test.py b/tests/identities/iddocumentverification/iddocumentverification_integration_test.py new file mode 100644 index 00000000..309b3724 --- /dev/null +++ b/tests/identities/iddocumentverification/iddocumentverification_integration_test.py @@ -0,0 +1,130 @@ +import pytest + +from checkout_sdk.identities.iddocumentverification.iddocumentverification import ( + IdDocumentVerificationRequest, IdDocumentVerificationAttemptRequest, DeclaredData +) +from tests.checkout_test_utils import assert_response, new_uuid + + +# tests + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_id_document_verification(default_api): + response = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + assert_id_document_verification_response(response) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_id_document_verification(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + retrieved = default_api.id_document_verification.get_id_document_verification(created.id) + assert_id_document_verification_response(retrieved) + assert retrieved.id == created.id + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_anonymize_id_document_verification(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + response = default_api.id_document_verification.anonymize_id_document_verification(created.id) + assert_response(response, 'http_metadata', 'id') + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_id_document_verification_attempt(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + attempt = default_api.id_document_verification.create_id_document_verification_attempt( + created.id, id_document_verification_attempt_request()) + assert_id_document_verification_attempt_response(attempt) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_id_document_verification_attempts(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + created_attempt = default_api.id_document_verification.create_id_document_verification_attempt( + created.id, id_document_verification_attempt_request()) + attempts = default_api.id_document_verification.get_id_document_verification_attempts(created.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + assert any(a.id == created_attempt.id for a in attempts.data) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_id_document_verification_attempt(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + created_attempt = default_api.id_document_verification.create_id_document_verification_attempt( + created.id, id_document_verification_attempt_request()) + retrieved = default_api.id_document_verification.get_id_document_verification_attempt( + created.id, created_attempt.id) + assert_id_document_verification_attempt_response(retrieved) + assert retrieved.id == created_attempt.id + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_id_document_verification_report(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + report = default_api.id_document_verification.get_id_document_verification_report(created.id) + assert_response(report, 'http_metadata', 'signed_url') + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_perform_id_document_verification_workflow(default_api): + created = default_api.id_document_verification.create_id_document_verification( + id_document_verification_request()) + assert_id_document_verification_response(created) + + retrieved = default_api.id_document_verification.get_id_document_verification(created.id) + assert_id_document_verification_response(retrieved) + assert retrieved.id == created.id + + attempt_request = id_document_verification_attempt_request() + created_attempt = default_api.id_document_verification.create_id_document_verification_attempt( + created.id, attempt_request) + assert_id_document_verification_attempt_response(created_attempt) + + attempts = default_api.id_document_verification.get_id_document_verification_attempts(created.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + + retrieved_attempt = default_api.id_document_verification.get_id_document_verification_attempt( + created.id, created_attempt.id) + assert_id_document_verification_attempt_response(retrieved_attempt) + assert retrieved_attempt.id == created_attempt.id + + report = default_api.id_document_verification.get_id_document_verification_report(created.id) + assert_response(report, 'http_metadata', 'signed_url') + + anonymized = default_api.id_document_verification.anonymize_id_document_verification(created.id) + assert_response(anonymized, 'http_metadata', 'id') + + +# common methods + +def id_document_verification_request() -> IdDocumentVerificationRequest: + declared_data = DeclaredData() + declared_data.name = 'John Doe' + + request = IdDocumentVerificationRequest() + request.applicant_id = new_uuid() + request.user_journey_id = new_uuid() + request.declared_data = declared_data + return request + + +def id_document_verification_attempt_request() -> IdDocumentVerificationAttemptRequest: + request = IdDocumentVerificationAttemptRequest() + request.document_front = 'base64-encoded-front-image-data' + request.document_back = 'base64-encoded-back-image-data' + return request + + +def assert_id_document_verification_response(response): + assert_response(response, 'http_metadata', 'id', 'applicant_id', 'status') + + +def assert_id_document_verification_attempt_response(response): + assert_response(response, 'http_metadata', 'id', 'status') diff --git a/tests/identities/identityverification/__init__.py b/tests/identities/identityverification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/identities/identityverification/identityverification_client_test.py b/tests/identities/identityverification/identityverification_client_test.py new file mode 100644 index 00000000..8fd8e815 --- /dev/null +++ b/tests/identities/identityverification/identityverification_client_test.py @@ -0,0 +1,49 @@ +import pytest + +from checkout_sdk.identities.identityverification.identityverification import ( + IdentityVerificationRequest, IdentityVerificationAndAttemptRequest, IdentityVerificationAttemptRequest +) +from checkout_sdk.identities.identityverification.identityverification_client import IdentityVerificationClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return IdentityVerificationClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +# tests + +class TestIdentityVerificationClient: + + def test_should_create_identity_verification_and_attempt(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_identity_verification_and_attempt(IdentityVerificationAndAttemptRequest()) == 'response' + + def test_should_create_identity_verification(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_identity_verification(IdentityVerificationRequest()) == 'response' + + def test_should_get_identity_verification(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification('idv_12345') == 'response' + + def test_should_anonymize_identity_verification(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_identity_verification('idv_12345') == 'response' + + def test_should_create_identity_verification_attempt(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_identity_verification_attempt('idv_12345', + IdentityVerificationAttemptRequest()) == 'response' + + def test_should_get_identity_verification_attempts(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification_attempts('idv_12345') == 'response' + + def test_should_get_identity_verification_attempt(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification_attempt('idv_12345', 'attempt_67890') == 'response' + + def test_should_get_identity_verification_report(self, mocker, client: IdentityVerificationClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification_report('idv_12345') == 'response' diff --git a/tests/identities/identityverification/identityverification_integration_test.py b/tests/identities/identityverification/identityverification_integration_test.py new file mode 100644 index 00000000..a69a4fbd --- /dev/null +++ b/tests/identities/identityverification/identityverification_integration_test.py @@ -0,0 +1,188 @@ +import pytest + +from checkout_sdk.identities.identityverification.identityverification import ( + IdentityVerificationRequest, IdentityVerificationAndAttemptRequest, + IdentityVerificationAttemptRequest, DeclaredData, ClientInformation +) +from tests.checkout_test_utils import assert_response, new_uuid + + +# tests + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_identity_verification_and_attempt(default_api): + response = default_api.identity_verification.create_identity_verification_and_attempt( + identity_verification_and_attempt_request()) + assert_identity_verification_and_attempt_response(response) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_identity_verification(default_api): + response = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + assert_identity_verification_response(response) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_identity_verification(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + retrieved = default_api.identity_verification.get_identity_verification(created.id) + assert_identity_verification_response(retrieved) + assert retrieved.id == created.id + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_anonymize_identity_verification(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + response = default_api.identity_verification.anonymize_identity_verification(created.id) + assert_response(response, 'http_metadata', 'id') + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_create_identity_verification_attempt(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + attempt = default_api.identity_verification.create_identity_verification_attempt( + created.id, identity_verification_attempt_request()) + assert_identity_verification_attempt_response(attempt) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_identity_verification_attempts(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + created_attempt = default_api.identity_verification.create_identity_verification_attempt( + created.id, identity_verification_attempt_request()) + attempts = default_api.identity_verification.get_identity_verification_attempts(created.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + assert any(a.id == created_attempt.id for a in attempts.data) + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_identity_verification_attempt(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + created_attempt = default_api.identity_verification.create_identity_verification_attempt( + created.id, identity_verification_attempt_request()) + retrieved = default_api.identity_verification.get_identity_verification_attempt( + created.id, created_attempt.id) + assert_identity_verification_attempt_response(retrieved) + assert retrieved.id == created_attempt.id + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_get_identity_verification_report(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + report = default_api.identity_verification.get_identity_verification_report(created.id) + assert_response(report, 'http_metadata', 'signed_url') + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_perform_complete_identity_verification_workflow(default_api): + created_with_attempt = default_api.identity_verification.create_identity_verification_and_attempt( + identity_verification_and_attempt_request()) + assert_identity_verification_and_attempt_response(created_with_attempt) + + retrieved = default_api.identity_verification.get_identity_verification(created_with_attempt.id) + assert_identity_verification_response(retrieved) + assert retrieved.id == created_with_attempt.id + + attempt = default_api.identity_verification.create_identity_verification_attempt( + created_with_attempt.id, identity_verification_attempt_request()) + assert_identity_verification_attempt_response(attempt) + + attempts = default_api.identity_verification.get_identity_verification_attempts(created_with_attempt.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + assert any(a.id == attempt.id for a in attempts.data) + + retrieved_attempt = default_api.identity_verification.get_identity_verification_attempt( + created_with_attempt.id, attempt.id) + assert_identity_verification_attempt_response(retrieved_attempt) + assert retrieved_attempt.id == attempt.id + + report = default_api.identity_verification.get_identity_verification_report(created_with_attempt.id) + assert_response(report, 'http_metadata', 'signed_url') + + anonymized = default_api.identity_verification.anonymize_identity_verification(created_with_attempt.id) + assert_response(anonymized, 'http_metadata', 'id') + + +@pytest.mark.skip(reason='Requires valid test environment setup') +def test_should_perform_separate_create_and_attempt_workflow(default_api): + created = default_api.identity_verification.create_identity_verification( + identity_verification_request()) + assert_identity_verification_response(created) + + retrieved = default_api.identity_verification.get_identity_verification(created.id) + assert_identity_verification_response(retrieved) + assert retrieved.id == created.id + + attempt = default_api.identity_verification.create_identity_verification_attempt( + created.id, identity_verification_attempt_request()) + assert_identity_verification_attempt_response(attempt) + + attempts = default_api.identity_verification.get_identity_verification_attempts(created.id) + assert_response(attempts, 'http_metadata', 'total_count', 'data') + assert any(a.id == attempt.id for a in attempts.data) + + retrieved_attempt = default_api.identity_verification.get_identity_verification_attempt( + created.id, attempt.id) + assert_identity_verification_attempt_response(retrieved_attempt) + assert retrieved_attempt.id == attempt.id + + report = default_api.identity_verification.get_identity_verification_report(created.id) + assert_response(report, 'http_metadata', 'signed_url') + + anonymized = default_api.identity_verification.anonymize_identity_verification(created.id) + assert_response(anonymized, 'http_metadata', 'id') + + +# common methods + +def identity_verification_and_attempt_request() -> IdentityVerificationAndAttemptRequest: + declared_data = DeclaredData() + declared_data.name = 'John Doe' + + request = IdentityVerificationAndAttemptRequest() + request.applicant_id = new_uuid() + request.user_journey_id = new_uuid() + request.redirect_url = 'https://example.com/redirect' + request.declared_data = declared_data + return request + + +def identity_verification_request() -> IdentityVerificationRequest: + declared_data = DeclaredData() + declared_data.name = 'John Doe' + + request = IdentityVerificationRequest() + request.applicant_id = new_uuid() + request.user_journey_id = new_uuid() + request.declared_data = declared_data + return request + + +def identity_verification_attempt_request() -> IdentityVerificationAttemptRequest: + client_information = ClientInformation() + client_information.pre_selected_residence_country = 'US' + client_information.pre_selected_language = 'en-US' + + request = IdentityVerificationAttemptRequest() + request.redirect_url = 'https://example.com/redirect' + request.client_information = client_information + return request + + +def assert_identity_verification_and_attempt_response(response): + assert_response(response, 'http_metadata', 'id', 'applicant_id', 'status', 'redirect_url') + + +def assert_identity_verification_response(response): + assert_response(response, 'http_metadata', 'id', 'applicant_id', 'status') + + +def assert_identity_verification_attempt_response(response): + assert_response(response, 'http_metadata', 'id', 'status') diff --git a/tests/payments/applepay/__init__.py b/tests/payments/applepay/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/payments/applepay/applepay_client_test.py b/tests/payments/applepay/applepay_client_test.py new file mode 100644 index 00000000..1e3cbf8e --- /dev/null +++ b/tests/payments/applepay/applepay_client_test.py @@ -0,0 +1,27 @@ +import pytest + +from checkout_sdk.payments.applepay.applepay import UploadCertificateRequest, EnrollDomainRequest, \ + GenerateSigningRequestRequest +from checkout_sdk.payments.applepay.applepay_client import ApplePayClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return ApplePayClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +# tests + +class TestApplePayClient: + + def test_should_upload_payment_processing_certificate(self, mocker, client: ApplePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.upload_payment_processing_certificate(UploadCertificateRequest()) == 'response' + + def test_should_enroll_domain(self, mocker, client: ApplePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.enroll_domain(EnrollDomainRequest()) == 'response' + + def test_should_generate_certificate_signing_request(self, mocker, client: ApplePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.generate_certificate_signing_request(GenerateSigningRequestRequest()) == 'response' diff --git a/tests/payments/applepay/applepay_integration_test.py b/tests/payments/applepay/applepay_integration_test.py new file mode 100644 index 00000000..8ea15c74 --- /dev/null +++ b/tests/payments/applepay/applepay_integration_test.py @@ -0,0 +1,82 @@ +from __future__ import absolute_import + +import pytest + +from checkout_sdk.payments.applepay.applepay import UploadCertificateRequest, EnrollDomainRequest, \ + GenerateSigningRequestRequest, ProtocolVersions +from tests.checkout_test_utils import assert_response + + +# tests + +@pytest.mark.skip(reason='Requires valid payment processing certificate') +def test_should_upload_payment_processing_certificate(default_api): + request = create_upload_certificate_request() + response = default_api.apple_pay.upload_payment_processing_certificate(request) + assert_upload_certificate_response(response) + + +@pytest.mark.skip(reason='Requires OAuth credentials and valid domain verification') +def test_should_enroll_domain(default_api): + request = create_enroll_domain_request() + response = default_api.apple_pay.enroll_domain(request) + assert response is not None + + +def test_should_generate_certificate_signing_request(default_api): + request = create_generate_signing_request(ProtocolVersions.EC_V1) + response = default_api.apple_pay.generate_certificate_signing_request(request) + assert_generate_signing_request_response(response) + + +def test_should_generate_certificate_signing_request_with_rsa_protocol(default_api): + request = create_generate_signing_request(ProtocolVersions.RSA_V1) + response = default_api.apple_pay.generate_certificate_signing_request(request) + assert_generate_signing_request_response(response) + + +# common methods + +def create_upload_certificate_request() -> UploadCertificateRequest: + request = UploadCertificateRequest() + request.content = ('MIIEfTCCBCOgAwIBAgIID/asezaWNycwCgYIKoZIzj0EAwIwgYAxNDAyBgNVBAMMK0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9w' + 'ZXIgUmVsYXRpb25zIENBIC0gRzIxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQK' + 'DApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0yMTA2MTExMzQ0MjVaFw0yMzA3MTExMzQ0MjRaMIGuMS0wKwYKCZIm' + 'iZPyLGQBAQwdbWVyY2hhbnQuY29tLmNoZWNrb3V0LnNhbmRib3gxQzBBBgNVBAMMOkFwcGxlIFBheSBQYXltZW50IFBy' + 'b2Nlc3Npbmc6bWVyY2hhbnQuY29tLmNoZWNrb3V0LnNhbmRib3gxEzARBgNVBAsMCkUzMlhCUUs0UTUxFjAUBgNVBAoM' + 'DUNoZWNrb3V0IEx0ZC4xCzAJBgNVBAYTAkdCMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsvyUM9D1cssldH+VPpt' + 'En4VAw/Q6ovJuHVlyBSRaPGLHFce04lCiT/xnXOWRkUxyCzQWKhfG2zo19u4s+evx7aOCAlUwggJRMAwGA1UdEwEB/w' + 'QCMAAwHwYDVR0jBBgwFoAUhLaEzDqGYnIWWZToGqO9SN863wswRwYIKwYBBQUHAQEEOzA5MDcGCCsGAQUFBzABhitod' + 'HRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxld3dkcmNhMjAxMIIBHQYDVR0gBIIBFDCCARAwggEMBgkqhkiG' + '92NkBQEwgf4wgcMGCCsGAQUFBwICMIG2DIGzUmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZpY2F0ZSBieSBhbnkgcGFydCBh' + 'c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW' + 'ducyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLj' + 'A2BggrBgEFBQcCAQYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDYGA1UdHwQvMC0wK6' + 'ApoCeGJWh0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxld3dkcmNhMi5jcmwwHQYDVR0OBBYEFE2G+vfc0O4zhDEFl3Xpr' + '4AJsegTMA4GA1UdDwEB/wQEAwIHIDBPBgoZhkiG92NkBgAEBEIMQDdGRjg0REI5MDE5NkVGN0I5RTc4NDZEMjg4NzZCNk' + 'JGRDU2RjM4MDlCNzUyNjAzRDM4QzcxNUJFMTY2M0JENEMwCgYIKoZIzj0EAwIDSAAwRQIgTjywMwOrLX3TwDUrPn7yDG' + 'L/dhc+VNudv0uGBOWRyXACIQClFQFvgx+hfTwVdHt8klrswpgtZtbYjs74p9GYuc8Puw==') + return request + + +def create_enroll_domain_request() -> EnrollDomainRequest: + request = EnrollDomainRequest() + request.domain = 'checkout-test-domain.com' + return request + + +def create_generate_signing_request(protocol_version: ProtocolVersions) -> GenerateSigningRequestRequest: + request = GenerateSigningRequestRequest() + request.protocol_version = protocol_version + return request + + +def assert_upload_certificate_response(response): + assert_response(response, 'http_metadata', 'id', 'public_key_hash', 'valid_from', 'valid_until') + assert response.valid_until > response.valid_from + + +def assert_generate_signing_request_response(response): + assert_response(response, 'http_metadata', 'content') + assert 'BEGIN CERTIFICATE REQUEST' in response.content + assert 'END CERTIFICATE REQUEST' in response.content diff --git a/tests/payments/googlepay/__init__.py b/tests/payments/googlepay/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/payments/googlepay/googlepay_client_test.py b/tests/payments/googlepay/googlepay_client_test.py new file mode 100644 index 00000000..08dc8b49 --- /dev/null +++ b/tests/payments/googlepay/googlepay_client_test.py @@ -0,0 +1,30 @@ +import pytest + +from checkout_sdk.payments.googlepay.googlepay import GooglePayEnrollmentRequest, GooglePayRegisterDomainRequest +from checkout_sdk.payments.googlepay.googlepay_client import GooglePayClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return GooglePayClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +# tests + +class TestGooglePayClient: + + def test_should_create_enrollment(self, mocker, client: GooglePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_enrollment(GooglePayEnrollmentRequest()) == 'response' + + def test_should_register_domain(self, mocker, client: GooglePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.register_domain('ent_uzm3uxtssvmuxnyrfdffcyjxeu', GooglePayRegisterDomainRequest()) == 'response' + + def test_should_get_registered_domains(self, mocker, client: GooglePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_registered_domains('ent_uzm3uxtssvmuxnyrfdffcyjxeu') == 'response' + + def test_should_get_enrollment_state(self, mocker, client: GooglePayClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_enrollment_state('ent_uzm3uxtssvmuxnyrfdffcyjxeu') == 'response' diff --git a/tests/payments/googlepay/googlepay_integration_test.py b/tests/payments/googlepay/googlepay_integration_test.py new file mode 100644 index 00000000..da26198b --- /dev/null +++ b/tests/payments/googlepay/googlepay_integration_test.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import + +import pytest + +from checkout_sdk.payments.googlepay.googlepay import GooglePayEnrollmentRequest, GooglePayRegisterDomainRequest +from tests.checkout_test_utils import assert_response + + +# tests + +@pytest.mark.skip(reason='Requires a valid entity with Google Pay enrollment permissions') +def test_should_create_enrollment(default_api): + request = create_enrollment_request() + response = default_api.google_pay.create_enrollment(request) + assert_enrollment_response(response) + + +@pytest.mark.skip(reason='Requires an actively enrolled Google Pay entity') +def test_should_register_domain(default_api): + request = create_register_domain_request() + response = default_api.google_pay.register_domain('ent_uzm3uxtssvmuxnyrfdffcyjxeu', request) + assert response is not None + + +@pytest.mark.skip(reason='Requires an actively enrolled Google Pay entity') +def test_should_get_registered_domains(default_api): + response = default_api.google_pay.get_registered_domains('ent_uzm3uxtssvmuxnyrfdffcyjxeu') + assert_registered_domains_response(response) + + +@pytest.mark.skip(reason='Requires an actively enrolled Google Pay entity') +def test_should_get_enrollment_state(default_api): + response = default_api.google_pay.get_enrollment_state('ent_uzm3uxtssvmuxnyrfdffcyjxeu') + assert_enrollment_state_response(response) + + +# common methods + +def create_enrollment_request() -> GooglePayEnrollmentRequest: + request = GooglePayEnrollmentRequest() + request.entity_id = 'ent_uzm3uxtssvmuxnyrfdffcyjxeu' + request.email_address = 'test@example.com' + request.accept_terms_of_service = True + return request + + +def create_register_domain_request() -> GooglePayRegisterDomainRequest: + request = GooglePayRegisterDomainRequest() + request.web_domain = 'checkout-test-domain.com' + return request + + +def assert_enrollment_response(response): + assert_response(response, 'http_metadata', 'tos_accepted_time', 'state') + + +def assert_registered_domains_response(response): + assert_response(response, 'http_metadata', 'domains') + + +def assert_enrollment_state_response(response): + assert_response(response, 'http_metadata', 'state') diff --git a/tests/standaloneaccountupdater/standalone_account_updater_client_test.py b/tests/standaloneaccountupdater/standalone_account_updater_client_test.py new file mode 100644 index 00000000..5901efa7 --- /dev/null +++ b/tests/standaloneaccountupdater/standalone_account_updater_client_test.py @@ -0,0 +1,16 @@ +import pytest + +from checkout_sdk.standaloneaccountupdater.standalone_account_updater import GetUpdatedCardCredentialsRequest +from checkout_sdk.standaloneaccountupdater.standalone_account_updater_client import StandaloneAccountUpdaterClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return StandaloneAccountUpdaterClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +class TestStandaloneAccountUpdaterClient: + + def test_should_get_updated_card_credentials(self, mocker, client: StandaloneAccountUpdaterClient): + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.get_updated_card_credentials(GetUpdatedCardCredentialsRequest()) == 'response' diff --git a/tests/standaloneaccountupdater/standalone_account_updater_integration_test.py b/tests/standaloneaccountupdater/standalone_account_updater_integration_test.py new file mode 100644 index 00000000..3bbdae82 --- /dev/null +++ b/tests/standaloneaccountupdater/standalone_account_updater_integration_test.py @@ -0,0 +1,84 @@ +import pytest + +from checkout_sdk.exception import CheckoutApiException +from checkout_sdk.standaloneaccountupdater.standalone_account_updater import ( + GetUpdatedCardCredentialsRequest, SourceOptions, CardDetailsRequest, InstrumentReference +) +from tests.checkout_test_utils import assert_response + + +# tests + +@pytest.mark.skip(reason='Requires valid account updater credentials and live card data') +def test_should_get_updated_card_credentials_with_card(oauth_api): + response = oauth_api.standalone_account_updater.get_updated_card_credentials( + card_credentials_request(2030) + ) + assert_updated_card_credentials_response(response) + + +@pytest.mark.skip(reason='Requires valid account updater credentials and live instrument data') +def test_should_get_updated_card_credentials_with_instrument(oauth_api): + response = oauth_api.standalone_account_updater.get_updated_card_credentials( + instrument_credentials_request('ins_v5rgkf3gdtpuzjqesyxmyodnya') + ) + assert_updated_card_credentials_response(response) + + +def test_should_throw_on_invalid_card_request(oauth_api): + with pytest.raises(CheckoutApiException): + oauth_api.standalone_account_updater.get_updated_card_credentials(invalid_card_credentials_request()) + + +def test_should_throw_422_on_standard_test_card(oauth_api): + try: + oauth_api.standalone_account_updater.get_updated_card_credentials(card_credentials_request(2026)) + pytest.fail() + except CheckoutApiException as err: + assert err.args[0] == 'The API response status code (422) does not indicate success.' + +# common functions + + +def card_credentials_request(expiry_year: int) -> GetUpdatedCardCredentialsRequest: + card = CardDetailsRequest() + card.number = '4242424242424242' + card.expiry_month = 12 + card.expiry_year = expiry_year + + source = SourceOptions() + source.card = card + + request = GetUpdatedCardCredentialsRequest() + request.source_options = source + return request + + +def instrument_credentials_request(instrument_id: str) -> GetUpdatedCardCredentialsRequest: + instrument = InstrumentReference() + instrument.id = instrument_id + + source = SourceOptions() + source.instrument = instrument + + request = GetUpdatedCardCredentialsRequest() + request.source_options = source + return request + + +def invalid_card_credentials_request() -> GetUpdatedCardCredentialsRequest: + card = CardDetailsRequest() + card.number = 'invalid_card_number' + card.expiry_month = 13 + card.expiry_year = 2020 + + source = SourceOptions() + source.card = card + + request = GetUpdatedCardCredentialsRequest() + request.source_options = source + return request + + +def assert_updated_card_credentials_response(response): + assert_response(response, 'http_metadata', 'account_update_status')