diff --git a/.gitignore b/.gitignore index 6fa2d975..6ba84ede 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ checkout_sdk.egg-info htmlcov .cursor/rules/ .cursor/skills/ +.cursor .vscode/settings.json - .claude/ CLAUDE.md diff --git a/checkout_sdk/accounts/accounts.py b/checkout_sdk/accounts/accounts.py index 503bbd94..3b9817b0 100644 --- a/checkout_sdk/accounts/accounts.py +++ b/checkout_sdk/accounts/accounts.py @@ -331,7 +331,7 @@ class AccountsPaymentInstrument: class PaymentInstrumentRequest: label: str - type = InstrumentType + type: InstrumentType currency: Currency country: Country default: bool diff --git a/checkout_sdk/balances/balances.py b/checkout_sdk/balances/balances.py index 495f337f..32b1be4c 100644 --- a/checkout_sdk/balances/balances.py +++ b/checkout_sdk/balances/balances.py @@ -1,2 +1,4 @@ class BalancesQuery: query: str + with_currency_account_id: str + balances_at: str diff --git a/checkout_sdk/checkout_api.py b/checkout_sdk/checkout_api.py index 07230fb4..7b006132 100644 --- a/checkout_sdk/checkout_api.py +++ b/checkout_sdk/checkout_api.py @@ -35,6 +35,8 @@ 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 +from checkout_sdk.networktokens.network_tokens_client import NetworkTokensClient +from checkout_sdk.paymentmethods.payment_methods_client import PaymentMethodsClient def _base_api_client(configuration: CheckoutConfiguration) -> ApiClient: @@ -111,3 +113,5 @@ def __init__(self, configuration: CheckoutConfiguration): self.applicants = ApplicantsClient(api_client=identity_api_client, configuration=configuration) self.identity_verification = IdentityVerificationClient(api_client=identity_api_client, configuration=configuration) + self.network_tokens = NetworkTokensClient(api_client=base_api_client, configuration=configuration) + self.payment_methods = PaymentMethodsClient(api_client=base_api_client, configuration=configuration) diff --git a/checkout_sdk/common/common.py b/checkout_sdk/common/common.py index 63f998bd..4cef37d4 100644 --- a/checkout_sdk/common/common.py +++ b/checkout_sdk/common/common.py @@ -23,6 +23,9 @@ class CustomerRequest: email: str name: str phone: Phone + default: str + metadata: dict + tax_number: str class CustomerRetry: diff --git a/checkout_sdk/common/enums.py b/checkout_sdk/common/enums.py index fb751399..fdae8682 100644 --- a/checkout_sdk/common/enums.py +++ b/checkout_sdk/common/enums.py @@ -476,8 +476,16 @@ class PaymentSourceType(str, Enum): OCTOPUS = 'octopus' PLAID = 'plaid' SEQURA = 'sequra' + MOBILEPAY = 'mobilepay' + PAYNOW = 'paynow' + SWISH = 'swish' + TWINT = 'twint' + VIPPS = 'vipps' + BLIK = 'blik' +# Used by ThreeDsRequest (in payments). The /sessions endpoint accepts +# additional exemption-like values — see SessionChallengeIndicator. class ChallengeIndicator(str, Enum): NO_PREFERENCE = 'no_preference' NO_CHALLENGE_REQUESTED = 'no_challenge_requested' @@ -490,6 +498,7 @@ class InstrumentType(str, Enum): TOKEN = 'token' CARD = 'card' SEPA = 'sepa' + ACH = 'ach' CARD_TOKEN = 'card_token' @@ -499,6 +508,22 @@ class AccountType(str, Enum): CASH = 'cash' +# ACH-specific account type. Distinct from AccountType because the ACH endpoint's +# accepted values are a different set (`savings`, `checking`) — sharing the +# AccountType enum here would let callers pass `current` or `cash` which the +# ACH API rejects. +class AchAccountType(str, Enum): + SAVINGS = 'savings' + CHECKING = 'checking' + + +# SEPA mandate type. Used by both RequestSepaV4Source.mandate_type and +# StoreSepaInstrumentRequest.instrument_data.type — same enum, two callsites. +class SepaMandateType(str, Enum): + CORE = 'Core' + B2B = 'B2B' + + class AccountHolderType(str, Enum): INDIVIDUAL = 'individual' CORPORATE = 'corporate' diff --git a/checkout_sdk/disputes/disputes.py b/checkout_sdk/disputes/disputes.py index e825db0c..93b623b5 100644 --- a/checkout_sdk/disputes/disputes.py +++ b/checkout_sdk/disputes/disputes.py @@ -15,6 +15,35 @@ class DisputesQueryFilter: entity_ids: str sub_entity_ids: str payment_mcc: str + processing_channel_ids: str + segment_ids: str + + +class CompellingEvidenceShippingAddress: + address: str + address2: str + city: str + state_region: str + postal_code: str + country: str + + +class CompellingEvidenceHistoricalTransaction: + historical_arn: str + merchandise_or_service_desc: str + + +class CompellingEvidence: + merchandise_or_service: str + merchandise_or_service_desc: str + merchandise_or_service_provided_date: datetime + shipping_delivery_status: str + tracking_information: str + user_id: str + ip_address: str + device_id: str + shipping_address: CompellingEvidenceShippingAddress + historical_transactions: list # CompellingEvidenceHistoricalTransaction class DisputeEvidenceRequest: @@ -34,3 +63,8 @@ class DisputeEvidenceRequest: additional_evidence_text: str proof_of_delivery_or_service_date_file: str proof_of_delivery_or_service_date_text: str + arbitration_no_review_files: list # list[str] + arbitration_no_review_text: str + arbitration_review_required_files: list # list[str] + arbitration_review_required_text: str + compelling_evidence: CompellingEvidence diff --git a/checkout_sdk/disputes/disputes_client.py b/checkout_sdk/disputes/disputes_client.py index 382782b4..ce4c6426 100644 --- a/checkout_sdk/disputes/disputes_client.py +++ b/checkout_sdk/disputes/disputes_client.py @@ -11,6 +11,7 @@ class DisputesClient(FilesClient): __DISPUTES_PATH = 'disputes' __ACCEPT_PATH = 'accept' __EVIDENCE_PATH = 'evidence' + __ARBITRATION_PATH = 'arbitration' __SUBMITTED_PATH = 'submitted' __SCHEME_FILES_PATH = "schemefiles" @@ -42,6 +43,13 @@ def submit_evidence(self, dispute_id: str): return self._api_client.post(self.build_path(self.__DISPUTES_PATH, dispute_id, self.__EVIDENCE_PATH), self._sdk_authorization()) + def submit_arbitration_evidence(self, dispute_id: str): + return self._api_client.post(self.build_path( + self.__DISPUTES_PATH, dispute_id, + self.__EVIDENCE_PATH, + self.__ARBITRATION_PATH), + self._sdk_authorization()) + def get_compiled_submitted_evidence(self, dispute_id: str): return self._api_client.get(self.build_path( self.__DISPUTES_PATH, dispute_id, @@ -49,6 +57,14 @@ def get_compiled_submitted_evidence(self, dispute_id: str): self.__SUBMITTED_PATH), self._sdk_authorization()) + def get_compiled_submitted_arbitration_evidence(self, dispute_id: str): + return self._api_client.get(self.build_path( + self.__DISPUTES_PATH, dispute_id, + self.__EVIDENCE_PATH, + self.__ARBITRATION_PATH, + self.__SUBMITTED_PATH), + self._sdk_authorization()) + def get_dispute_scheme_files(self, dispute_id: str): return self._api_client.get(self.build_path(self.__DISPUTES_PATH, dispute_id, self.__SCHEME_FILES_PATH), self._sdk_authorization()) diff --git a/checkout_sdk/forex/forex.py b/checkout_sdk/forex/forex.py index c5f70304..c8056515 100644 --- a/checkout_sdk/forex/forex.py +++ b/checkout_sdk/forex/forex.py @@ -20,4 +20,4 @@ class RatesQueryFilter: product: str source: ForexSource currency_pairs: str - process_channel_id: str + processing_channel_id: str diff --git a/checkout_sdk/forex/forex_client.py b/checkout_sdk/forex/forex_client.py index 1ab8cf87..bb637a5a 100644 --- a/checkout_sdk/forex/forex_client.py +++ b/checkout_sdk/forex/forex_client.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +from warnings import warn + from checkout_sdk.api_client import ApiClient from checkout_sdk.authorization_type import AuthorizationType from checkout_sdk.checkout_configuration import CheckoutConfiguration @@ -18,6 +20,9 @@ def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): authorization_type=AuthorizationType.OAUTH) def request_quote(self, quote_request: QuoteRequest): + # Deprecated: 2023-05-31 The /forex/quotes endpoint was removed from the API. Use get_rates instead. + warn('Deprecated: /forex/quotes endpoint was removed from the API. Use get_rates instead.', + DeprecationWarning, stacklevel=2) return self._api_client.post(self.build_path(self.__FOREX_PATH, self.__QUOTES_PATH), self._sdk_authorization(), quote_request) diff --git a/checkout_sdk/forward/forward.py b/checkout_sdk/forward/forward.py index aba285ea..7c579819 100644 --- a/checkout_sdk/forward/forward.py +++ b/checkout_sdk/forward/forward.py @@ -66,12 +66,19 @@ class Headers: encrypted: str = None +class ForwardKeyValue: + name: str + value: str + + class DestinationRequest: url: str method: MethodType headers: Headers body: str signature: DlocalSignature = None + query: list # ForwardKeyValue + variables: list # ForwardKeyValue class NetworkToken: diff --git a/checkout_sdk/instruments/instruments.py b/checkout_sdk/instruments/instruments.py index 6c8a6f45..e982f5b5 100644 --- a/checkout_sdk/instruments/instruments.py +++ b/checkout_sdk/instruments/instruments.py @@ -2,7 +2,9 @@ from enum import Enum from checkout_sdk.common.common import BankDetails, UpdateCustomerRequest, AccountHolder, Phone -from checkout_sdk.common.enums import AccountType, AccountHolderType, Currency, Country, InstrumentType +from checkout_sdk.common.enums import ( + AccountType, AccountHolderType, AchAccountType, Currency, Country, InstrumentType, SepaMandateType, +) from checkout_sdk.payments.payments import PaymentType @@ -38,6 +40,13 @@ class InstrumentData: payment_type: PaymentType mandate_id: str date_of_signature: datetime + # SEPA mandate type — set when this InstrumentData is the SEPA variant. + type: SepaMandateType + # ACH-only fields below — set when this InstrumentData is the ACH variant. + # Distinct from AccountType (which serves the bank-account instrument endpoint + # with savings/current/cash) — ACH has its own value set. + account_type: AchAccountType + bank_code: str class CreateSepaInstrumentRequest(CreateInstrumentRequest): @@ -58,6 +67,7 @@ class CreateBankAccountInstrumentRequest(CreateInstrumentRequest): bban: str swift_bic: str currency: Currency + country: Country processing_channel_id: str account_holder: AccountHolder bank_details: BankDetails @@ -67,6 +77,27 @@ def __init__(self): super().__init__(InstrumentType.BANK_ACCOUNT) +class CreateCardInstrumentRequest(CreateInstrumentRequest): + number: str + expiry_month: int + expiry_year: int + network_token: str + processing_channel_id: str + entity_id: str + account_holder: AccountHolder + + def __init__(self): + super().__init__(InstrumentType.CARD) + + +class CreateAchInstrumentRequest(CreateInstrumentRequest): + instrument_data: InstrumentData + account_holder: AccountHolder + + def __init__(self): + super().__init__(InstrumentType.ACH) + + # Update class UpdateInstrumentRequest: type: InstrumentType diff --git a/checkout_sdk/issuing/cards.py b/checkout_sdk/issuing/cards.py index d905f97c..a6f3c52a 100644 --- a/checkout_sdk/issuing/cards.py +++ b/checkout_sdk/issuing/cards.py @@ -24,17 +24,34 @@ class SuspendReason(str, Enum): SUSPECTED_STOLEN = 'suspected_stolen' +class ReturnCredentials(str, Enum): + NUMBER = 'number' + CVC2 = 'cvc2' + + class CardLifetime: unit: LifetimeUnit value: int class ShippingInstructions: - recipient_address: str + # Deprecated: marked deprecated in the Checkout.com API swagger + # (IssuingShippingInstruction.shipping_recipient). Do not set in new code. + shipping_recipient: str shipping_address: Address + # Deprecated: marked deprecated in the Checkout.com API swagger + # (IssuingShippingInstruction.additional_comment). Do not set in new code. additional_comment: str +class CardMetadata: + udf1: str + udf2: str + udf3: str + udf4: str + udf5: str + + class CardRequest: type: CardType cardholder_id: str @@ -43,6 +60,8 @@ class CardRequest: card_product_id: str display_name: str activate_card: bool + metadata: CardMetadata + revocation_date: str def __init__(self, type_p: CardType): self.type = type_p @@ -57,11 +76,39 @@ def __init__(self): class VirtualCardRequest(CardRequest): is_single_use: bool + return_credentials: list # ReturnCredentials + control_profiles: list # str (IssuingControlProfileId) + controls: list # VirtualCardControlRequest def __init__(self): super().__init__(CardType.VIRTUAL) +class UpdateCardRequest: + reference: str + metadata: CardMetadata + expiry_month: int + expiry_year: int + + +class RenewCardRequest: + display_name: str + reference: str + metadata: CardMetadata + + +class PhysicalCardRenewRequest(RenewCardRequest): + shipping_instructions: ShippingInstructions + + +class VirtualCardRenewRequest(RenewCardRequest): + pass + + +class ScheduleCardRevocationRequest: + revocation_date: str + + class SecurityPair: question: str answer: str diff --git a/checkout_sdk/issuing/controls.py b/checkout_sdk/issuing/controls.py index 8be845cc..588be6f5 100644 --- a/checkout_sdk/issuing/controls.py +++ b/checkout_sdk/issuing/controls.py @@ -4,6 +4,7 @@ class ControlType(str, Enum): VELOCITY_LIMIT = 'velocity_limit' MCC_LIMIT = 'mcc_limit' + MID_LIMIT = 'mid_limit' class VelocityWindowType(str, Enum): @@ -18,6 +19,16 @@ class MccLimitType(str, Enum): BLOCK = 'block' +class MidLimitType(str, Enum): + ALLOW = 'allow' + BLOCK = 'block' + + +class FailIfType(str, Enum): + ALL_FAIL = 'all_fail' + ANY_FAIL = 'any_fail' + + class VelocityWindow: type: VelocityWindowType @@ -26,6 +37,7 @@ class VelocityLimit: amount_limit: int velocity_window: VelocityWindow mcc_list: list # str + mid_list: list # str class MccLimit: @@ -33,6 +45,11 @@ class MccLimit: mcc_list: list # str +class MidLimit: + type: MidLimitType + mid_list: list # str + + class CardControlRequest: description: str control_type: ControlType @@ -56,6 +73,41 @@ def __init__(self): super().__init__(ControlType.MCC_LIMIT) +# Parallel hierarchy for controls declared INLINE on VirtualCardRequest.controls. +# The standalone POST /issuing/controls endpoint requires target_id (a separate +# card to attach the control to). The inline variant does NOT — the card being +# created is the implicit target. Reusing CardControlRequest here would let +# callers set target_id on the wire, which the API ignores or rejects. These +# classes prevent that misuse at the type level. +class VirtualCardControlRequest: + description: str + control_type: ControlType + + def __init__(self, control_type: ControlType): + self.control_type = control_type + + +class VirtualCardVelocityControlRequest(VirtualCardControlRequest): + velocity_limit: VelocityLimit + + def __init__(self): + super().__init__(ControlType.VELOCITY_LIMIT) + + +class VirtualCardMccControlRequest(VirtualCardControlRequest): + mcc_limit: MccLimit + + def __init__(self): + super().__init__(ControlType.MCC_LIMIT) + + +class VirtualCardMidControlRequest(VirtualCardControlRequest): + mid_limit: MidLimit + + def __init__(self): + super().__init__(ControlType.MID_LIMIT) + + class CardControlsQuery: target_id: str @@ -64,3 +116,47 @@ class UpdateCardControlRequest: description: str velocity_limit: VelocityLimit mcc_limit: MccLimit + + +class ControlGroupControl: + control_type: ControlType + description: str + + def __init__(self, control_type: ControlType): + self.control_type = control_type + + +class MccControlGroupControl(ControlGroupControl): + mcc_limit: MccLimit + + def __init__(self): + super().__init__(ControlType.MCC_LIMIT) + + +class MidControlGroupControl(ControlGroupControl): + mid_limit: MidLimit + + def __init__(self): + super().__init__(ControlType.MID_LIMIT) + + +class VelocityControlGroupControl(ControlGroupControl): + velocity_limit: VelocityLimit + + def __init__(self): + super().__init__(ControlType.VELOCITY_LIMIT) + + +class CreateControlGroupRequest: + target_id: str + fail_if: FailIfType + controls: list # ControlGroupControl + description: str + + +class ControlGroupQueryTarget: + target_id: str + + +class ControlProfileRequest: + name: str diff --git a/checkout_sdk/issuing/disputes.py b/checkout_sdk/issuing/disputes.py new file mode 100644 index 00000000..76d73945 --- /dev/null +++ b/checkout_sdk/issuing/disputes.py @@ -0,0 +1,25 @@ +class DisputeEvidence: + name: str + content: str + description: str + + +class DisputeReasonChange: + reason: str + justification: str + + +class CreateDisputeRequest: + transaction_id: str + reason: str + evidence: list # DisputeEvidence + amount: int + presentment_message_id: str + justification: str + + +class EscalateDisputeRequest: + justification: str + additional_evidence: list # DisputeEvidence + amount: int + reason_change: DisputeReasonChange diff --git a/checkout_sdk/issuing/issuing_client.py b/checkout_sdk/issuing/issuing_client.py index 0148aeea..af506053 100644 --- a/checkout_sdk/issuing/issuing_client.py +++ b/checkout_sdk/issuing/issuing_client.py @@ -6,9 +6,14 @@ from checkout_sdk.client import Client from checkout_sdk.issuing.cardholders import CardholderRequest from checkout_sdk.issuing.cards import CardRequest, ThreeDsEnrollmentRequest, UpdateThreeDsEnrollmentRequest, \ - CardCredentialsQuery, RevokeRequest, SuspendRequest -from checkout_sdk.issuing.controls import CardControlRequest, CardControlsQuery, UpdateCardControlRequest -from checkout_sdk.issuing.testing import CardAuthorizationRequest, SimulationRequest + CardCredentialsQuery, RevokeRequest, SuspendRequest, UpdateCardRequest, RenewCardRequest, \ + ScheduleCardRevocationRequest +from checkout_sdk.issuing.controls import CardControlRequest, CardControlsQuery, UpdateCardControlRequest, \ + CreateControlGroupRequest, ControlGroupQueryTarget, ControlProfileRequest +from checkout_sdk.issuing.disputes import CreateDisputeRequest, EscalateDisputeRequest +from checkout_sdk.issuing.testing import CardAuthorizationRequest, SimulationRequest, \ + CardRefundAuthorizationRequest, SimulateOobAuthenticationRequest +from checkout_sdk.issuing.transactions import TransactionsQueryFilter class IssuingClient(Client): @@ -18,13 +23,27 @@ class IssuingClient(Client): __THREE_DS = '3ds-enrollment' __ACTIVATE = 'activate' __CREDENTIALS = 'credentials' + __RENEW = 'renew' __REVOKE = 'revoke' + __SCHEDULE_REVOCATION = 'schedule-revocation' __SUSPEND = 'suspend' __CONTROLS = 'controls' + __CONTROL_GROUPS = 'control-groups' + __CONTROL_PROFILES = 'control-profiles' + __ADD = 'add' + __REMOVE = 'remove' + __DIGITAL_CARDS = 'digital-cards' + __TRANSACTIONS = 'transactions' + __DISPUTES = 'disputes' + __CANCEL = 'cancel' + __ESCALATE = 'escalate' __SIMULATE = 'simulate' __AUTHORIZATIONS = 'authorizations' __PRESENTMENTS = 'presentments' __REVERSALS = 'reversals' + __REFUNDS = 'refunds' + __OOB = 'oob' + __AUTHENTICATION = 'authentication' def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): super().__init__(api_client=api_client, @@ -40,6 +59,11 @@ def get_cardholder(self, cardholder_id: str): return self._api_client.get(self.build_path(self.__ISSUING, self.__CARDHOLDERS, cardholder_id), self._sdk_authorization()) + def update_cardholder(self, cardholder_id: str, cardholder_request: CardholderRequest): + return self._api_client.patch(self.build_path(self.__ISSUING, self.__CARDHOLDERS, cardholder_id), + self._sdk_authorization(), + cardholder_request) + def get_cardholder_cards(self, cardholder_id: str): return self._api_client.get(self.build_path(self.__ISSUING, self.__CARDHOLDERS, cardholder_id, self.__CARDS), self._sdk_authorization()) @@ -53,6 +77,11 @@ def get_card_details(self, card_id: str): return self._api_client.get(self.build_path(self.__ISSUING, self.__CARDS, card_id), self._sdk_authorization()) + def update_card(self, card_id: str, update_card_request: UpdateCardRequest): + return self._api_client.patch(self.build_path(self.__ISSUING, self.__CARDS, card_id), + self._sdk_authorization(), + update_card_request) + def enroll_three_ds(self, card_id: str, three_ds_enrollment_request: ThreeDsEnrollmentRequest): return self._api_client.post(self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__THREE_DS), self._sdk_authorization(), @@ -76,16 +105,45 @@ def get_card_credentials(self, card_id: str, card_credentials_query: CardCredent self._sdk_authorization(), card_credentials_query) + def renew_card(self, card_id: str, renew_card_request: RenewCardRequest): + return self._api_client.post(self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__RENEW), + self._sdk_authorization(), + renew_card_request) + def revoke_card(self, card_id: str, revoke_request: RevokeRequest): return self._api_client.post(self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__REVOKE), self._sdk_authorization(), revoke_request) + def schedule_card_revocation(self, card_id: str, schedule_request: ScheduleCardRevocationRequest): + return self._api_client.post( + self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__SCHEDULE_REVOCATION), + self._sdk_authorization(), + schedule_request) + + def delete_card_revocation(self, card_id: str): + return self._api_client.delete( + self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__SCHEDULE_REVOCATION), + self._sdk_authorization()) + def suspend_card(self, card_id: str, suspend_request: SuspendRequest): return self._api_client.post(self.build_path(self.__ISSUING, self.__CARDS, card_id, self.__SUSPEND), self._sdk_authorization(), suspend_request) + def get_digital_card(self, digital_card_id: str): + return self._api_client.get(self.build_path(self.__ISSUING, self.__DIGITAL_CARDS, digital_card_id), + self._sdk_authorization()) + + def get_list_transactions(self, query: TransactionsQueryFilter): + return self._api_client.get(self.build_path(self.__ISSUING, self.__TRANSACTIONS), + self._sdk_authorization(), + query) + + def get_single_transaction(self, transaction_id: str): + return self._api_client.get(self.build_path(self.__ISSUING, self.__TRANSACTIONS, transaction_id), + self._sdk_authorization()) + def create_control(self, control_request: CardControlRequest): return self._api_client.post(self.build_path(self.__ISSUING, self.__CONTROLS), self._sdk_authorization(), @@ -109,6 +167,87 @@ def remove_control(self, control_id: str): return self._api_client.delete(self.build_path(self.__ISSUING, self.__CONTROLS, control_id), self._sdk_authorization()) + def create_control_group(self, create_control_group_request: CreateControlGroupRequest): + return self._api_client.post(self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_GROUPS), + self._sdk_authorization(), + create_control_group_request) + + def get_target_control_groups(self, query: ControlGroupQueryTarget): + return self._api_client.get(self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_GROUPS), + self._sdk_authorization(), + query) + + def get_control_group_details(self, control_group_id: str): + return self._api_client.get( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_GROUPS, control_group_id), + self._sdk_authorization()) + + def delete_control_group(self, control_group_id: str): + return self._api_client.delete( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_GROUPS, control_group_id), + self._sdk_authorization()) + + def create_control_profile(self, control_profile_request: ControlProfileRequest): + return self._api_client.post(self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES), + self._sdk_authorization(), + control_profile_request) + + def get_all_control_profiles(self, query: ControlGroupQueryTarget): + return self._api_client.get(self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES), + self._sdk_authorization(), + query) + + def get_control_profile_details(self, control_profile_id: str): + return self._api_client.get( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES, control_profile_id), + self._sdk_authorization()) + + def update_control_profile(self, control_profile_id: str, control_profile_request: ControlProfileRequest): + return self._api_client.patch( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES, control_profile_id), + self._sdk_authorization(), + control_profile_request) + + def delete_control_profile(self, control_profile_id: str): + return self._api_client.delete( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES, control_profile_id), + self._sdk_authorization()) + + def add_target_to_control_profile(self, control_profile_id: str, target_id: str): + return self._api_client.post( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES, control_profile_id, + self.__ADD, target_id), + self._sdk_authorization()) + + def remove_target_from_control_profile(self, control_profile_id: str, target_id: str): + return self._api_client.post( + self.build_path(self.__ISSUING, self.__CONTROLS, self.__CONTROL_PROFILES, control_profile_id, + self.__REMOVE, target_id), + self._sdk_authorization()) + + def create_dispute(self, create_dispute_request: CreateDisputeRequest, idempotency_key: str = None): + return self._api_client.post(self.build_path(self.__ISSUING, self.__DISPUTES), + self._sdk_authorization(), + create_dispute_request, + idempotency_key) + + def get_dispute_details(self, dispute_id: str): + return self._api_client.get(self.build_path(self.__ISSUING, self.__DISPUTES, dispute_id), + self._sdk_authorization()) + + def cancel_dispute(self, dispute_id: str, idempotency_key: str = None): + return self._api_client.post(self.build_path(self.__ISSUING, self.__DISPUTES, dispute_id, self.__CANCEL), + self._sdk_authorization(), + None, + idempotency_key) + + def escalate_dispute(self, dispute_id: str, escalate_dispute_request: EscalateDisputeRequest, + idempotency_key: str = None): + return self._api_client.post(self.build_path(self.__ISSUING, self.__DISPUTES, dispute_id, self.__ESCALATE), + self._sdk_authorization(), + escalate_dispute_request, + idempotency_key) + def simulate_authorization(self, authorization_request: CardAuthorizationRequest): return self._api_client.post(self.build_path(self.__ISSUING, self.__SIMULATE, self.__AUTHORIZATIONS), self._sdk_authorization(), @@ -134,3 +273,16 @@ def simulate_reversal(self, transaction_id: str, reversal_request: SimulationReq self.__ISSUING, self.__SIMULATE, self.__AUTHORIZATIONS, transaction_id, self.__REVERSALS), self._sdk_authorization(), reversal_request) + + def simulate_refund(self, transaction_id: str, refund_request: CardRefundAuthorizationRequest): + return self._api_client.post( + self.build_path( + self.__ISSUING, self.__SIMULATE, self.__AUTHORIZATIONS, transaction_id, self.__REFUNDS), + self._sdk_authorization(), + refund_request) + + def simulate_oob_authentication(self, simulate_oob_request: SimulateOobAuthenticationRequest): + return self._api_client.post( + self.build_path(self.__ISSUING, self.__SIMULATE, self.__OOB, self.__AUTHENTICATION), + self._sdk_authorization(), + simulate_oob_request) diff --git a/checkout_sdk/issuing/testing.py b/checkout_sdk/issuing/testing.py index 6142d148..68f7b6ff 100644 --- a/checkout_sdk/issuing/testing.py +++ b/checkout_sdk/issuing/testing.py @@ -37,3 +37,19 @@ class CardAuthorizationRequest: class SimulationRequest: amount: int + + +class CardRefundAuthorizationRequest: + amount: int + + +class OobSimulateTransactionDetails: + last_four: str + merchant_name: str + purchase_amount: int + purchase_currency: Currency + + +class SimulateOobAuthenticationRequest: + card_id: str + transaction_details: OobSimulateTransactionDetails diff --git a/checkout_sdk/issuing/transactions.py b/checkout_sdk/issuing/transactions.py new file mode 100644 index 00000000..38ed8887 --- /dev/null +++ b/checkout_sdk/issuing/transactions.py @@ -0,0 +1,21 @@ +from enum import Enum + +from checkout_sdk.common.common import QueryFilterDateRange + + +class TransactionStatus(str, Enum): + AUTHORIZED = 'authorized' + DECLINED = 'declined' + CANCELED = 'canceled' + CLEARED = 'cleared' + REFUNDED = 'refunded' + DISPUTED = 'disputed' + + +class TransactionsQueryFilter(QueryFilterDateRange): + limit: int + skip: int + cardholder_id: str + card_id: str + entity_id: str + status: TransactionStatus diff --git a/checkout_sdk/json_serializer.py b/checkout_sdk/json_serializer.py index 3907696c..d1276ed9 100644 --- a/checkout_sdk/json_serializer.py +++ b/checkout_sdk/json_serializer.py @@ -7,7 +7,9 @@ class JsonSerializer(json.JSONEncoder): 'account_holder_type': 'account-holder-type', 'payment_network': 'payment-network', 'from_': 'from', - 'if_match': 'if-match'} + 'if_match': 'if-match', + 'with_currency_account_id': 'withCurrencyAccountId', + 'balances_at': 'balancesAt'} def default(self, obj): if hasattr(obj, 'to_json'): diff --git a/checkout_sdk/metadata/metadata.py b/checkout_sdk/metadata/metadata.py index b8fa4e08..17e24122 100644 --- a/checkout_sdk/metadata/metadata.py +++ b/checkout_sdk/metadata/metadata.py @@ -51,3 +51,4 @@ def __init__(self): class CardMetadataRequest: source: CardMetadataRequestSource format: CardMetadataFormatType + reference: str diff --git a/checkout_sdk/networktokens/__init__.py b/checkout_sdk/networktokens/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/networktokens/network_tokens.py b/checkout_sdk/networktokens/network_tokens.py new file mode 100644 index 00000000..05276016 --- /dev/null +++ b/checkout_sdk/networktokens/network_tokens.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import + +from enum import Enum + +from checkout_sdk.common.enums import PaymentSourceType + + +class NetworkTokenTransactionType(str, Enum): + ECOM = 'ecom' + RECURRING = 'recurring' + POS = 'pos' + AFT = 'aft' + + +class NetworkTokenInitiatedBy(str, Enum): + CARDHOLDER = 'cardholder' + TOKEN_REQUESTOR = 'token_requestor' + + +class NetworkTokenDeleteReason(str, Enum): + FRAUD = 'fraud' + OTHER = 'other' + + +# Network Token Request Source +class NetworkTokenRequestSource: + type: PaymentSourceType + + def __init__(self, type_p: PaymentSourceType): + self.type = type_p + + +class NetworkTokenRequestCardSource(NetworkTokenRequestSource): + number: str + expiry_month: str + expiry_year: str + cvv: str + + def __init__(self): + super().__init__(PaymentSourceType.CARD) + + +class NetworkTokenRequestIdSource(NetworkTokenRequestSource): + id: str + + def __init__(self): + super().__init__(PaymentSourceType.ID) + + +# Provision Network Token +class ProvisionNetworkTokenRequest: + source: NetworkTokenRequestSource + + +# Provision Cryptogram +class RequestCryptogramRequest: + transaction_type: NetworkTokenTransactionType + + +# Delete Network Token +class DeleteNetworkTokenRequest: + initiated_by: NetworkTokenInitiatedBy + reason: NetworkTokenDeleteReason diff --git a/checkout_sdk/networktokens/network_tokens_client.py b/checkout_sdk/networktokens/network_tokens_client.py new file mode 100644 index 00000000..d4537641 --- /dev/null +++ b/checkout_sdk/networktokens/network_tokens_client.py @@ -0,0 +1,42 @@ +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.networktokens.network_tokens import ProvisionNetworkTokenRequest, RequestCryptogramRequest, \ + DeleteNetworkTokenRequest + + +class NetworkTokensClient(Client): + __NETWORK_TOKENS_PATH = 'network-tokens' + __CRYPTOGRAMS_PATH = 'cryptograms' + __DELETE_PATH = 'delete' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.OAUTH) + + def provision_network_token(self, provision_network_token_request: ProvisionNetworkTokenRequest): + return self._api_client.post(self.__NETWORK_TOKENS_PATH, + self._sdk_authorization(), + provision_network_token_request) + + def get_network_token(self, network_token_id: str): + return self._api_client.get(self.build_path(self.__NETWORK_TOKENS_PATH, network_token_id), + self._sdk_authorization()) + + def request_cryptogram(self, network_token_id: str, + request_cryptogram_request: RequestCryptogramRequest): + return self._api_client.post( + self.build_path(self.__NETWORK_TOKENS_PATH, network_token_id, self.__CRYPTOGRAMS_PATH), + self._sdk_authorization(), + request_cryptogram_request) + + def delete_network_token(self, network_token_id: str, + delete_network_token_request: DeleteNetworkTokenRequest): + return self._api_client.patch( + self.build_path(self.__NETWORK_TOKENS_PATH, network_token_id, self.__DELETE_PATH), + self._sdk_authorization(), + delete_network_token_request) diff --git a/checkout_sdk/oauth_scopes.py b/checkout_sdk/oauth_scopes.py index b1583534..163fdf55 100644 --- a/checkout_sdk/oauth_scopes.py +++ b/checkout_sdk/oauth_scopes.py @@ -4,71 +4,73 @@ class OAuthScopes(str, Enum): - VAULT = 'vault' - VAULT_INSTRUMENTS = 'vault:instruments' - VAULT_TOKENIZATION = 'vault:tokenization' - VAULT_CUSTOMERS = 'vault:customers' - VAULT_REAL_TIME_ACCOUNT_UPDATER = 'vault:real-time-account-updater' - 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' - GATEWAY_PAYMENT_AUTHORIZATION = 'gateway:payment-authorizations' - GATEWAY_PAYMENT_VOIDS = 'gateway:payment-voids' - GATEWAY_PAYMENT_CAPTURES = 'gateway:payment-captures' - GATEWAY_PAYMENT_REFUNDS = 'gateway:payment-refunds' - GATEWAY_PAYMENT_CANCELLATIONS = 'gateway:payment-cancellations' - GATEWAY_PAYMENT_CONTEXTS = 'gateway:payment-contexts' - FX = 'fx' - PAYOUTS_BANK_DETAILS = 'payouts:bank-details' - SESSIONS_APP = 'sessions:app' - SESSIONS_BROWSER = 'sessions:browser' + ACCOUNTS = 'accounts' + BALANCES = 'balances' + BALANCES_VIEW = 'balances:view' + CARD_MANAGEMENT = 'card-management' DISPUTES = 'disputes' - DISPUTES_VIEW = 'disputes:view' - DISPUTES_PROVIDE_EVIDENCE = 'disputes:provide-evidence' DISPUTES_ACCEPT = 'disputes:accept' + DISPUTES_PROVIDE_EVIDENCE = 'disputes:provide-evidence' DISPUTES_SCHEME_FILES = 'disputes:scheme-files' - MARKETPLACE = 'marketplace' - ACCOUNTS = 'accounts' - TRANSFERS = 'transfers' - TRANSFERS_CREATE = 'transfers:create' - TRANSFERS_VIEW = 'transfers:view' - FLOW = 'flow' - FLOW_WORKFLOWS = 'flow:workflows' - FLOW_EVENTS = 'flow:events' - FLOW_REFLOW = 'flow:reflow' + DISPUTES_VIEW = 'disputes:view' FILES = 'files' + FILES_DOWNLOAD = 'files:download' FILES_RETRIEVE = 'files:retrieve' FILES_UPLOAD = 'files:upload' - FILES_DOWNLOAD = 'files:download' - BALANCES = 'balances' - BALANCES_VIEW = 'balances:view' - MIDDLEWARE = 'middleware' - MIDDLEWARE_MERCHANTS_SECRET = 'middleware:merchants-secret' - MIDDLEWARE_MERCHANTS_PUBLIC = 'middleware:merchants-public' - REPORTS = 'reports' - REPORTS_VIEW = 'reports:view' FINANCIAL_ACTIONS = 'financial-actions' FINANCIAL_ACTIONS_VIEW = 'financial-actions:view' - CARD_MANAGEMENT = 'card-management' - ISSUING_CARD_MGMT = 'issuing:card-mgmt' + FLOW = 'flow' + FLOW_EVENTS = 'flow:events' + FLOW_REFLOW = 'flow:reflow' + FLOW_WORKFLOWS = 'flow:workflows' + FORWARD = 'forward' + FORWARD_SECRETS = 'forward:secrets' + FX = 'fx' + GATEWAY = 'gateway' + GATEWAY_PAYMENT = 'gateway:payment' + GATEWAY_PAYMENT_AUTHORIZATION = 'gateway:payment-authorizations' + GATEWAY_PAYMENT_CANCELLATIONS = 'gateway:payment-cancellations' + GATEWAY_PAYMENT_CAPTURES = 'gateway:payment-captures' + GATEWAY_PAYMENT_CONTEXTS = 'gateway:payment-contexts' + GATEWAY_PAYMENT_DETAILS = 'gateway:payment-details' + GATEWAY_PAYMENT_REFUNDS = 'gateway:payment-refunds' + GATEWAY_PAYMENT_VOIDS = 'gateway:payment-voids' + IDENTITY_VERIFICATION = 'identity-verification' ISSUING_CARD_MANAGEMENT_READ = 'issuing:card-management-read' ISSUING_CARD_MANAGEMENT_WRITE = 'issuing:card-management-write' + ISSUING_CARD_MGMT = 'issuing:card-mgmt' ISSUING_CLIENT = 'issuing:client' ISSUING_CONTROLS_READ = 'issuing:controls-read' ISSUING_CONTROLS_WRITE = 'issuing:controls-write' - ISSUING_TRANSACTIONS_READ = 'issuing:transactions-read' - ISSUING_TRANSACTIONS_WRITE = 'issuing:transactions-write' ISSUING_DISPUTES = 'issuing-disputes' ISSUING_DISPUTES_READ = 'issuing:disputes-read' ISSUING_DISPUTES_WRITE = 'issuing:disputes-write' - TRANSACTIONS = 'transactions' - IDENTITY_VERIFICATION = 'identity-verification' + ISSUING_TRANSACTIONS_READ = 'issuing:transactions-read' + ISSUING_TRANSACTIONS_WRITE = 'issuing:transactions-write' + MARKETPLACE = 'marketplace' + MIDDLEWARE = 'middleware' + MIDDLEWARE_GATEWAY = 'middleware:gateway' + MIDDLEWARE_MERCHANTS_PUBLIC = 'middleware:merchants-public' + MIDDLEWARE_MERCHANTS_SECRET = 'middleware:merchants-secret' + MIDDLEWARE_PAYMENT_CONTEXT = 'middleware:payment-context' + PAYMENTS_SEARCH = 'payments:search' PAYMENT_CONTEXT = 'Payment Context' - FORWARD = 'forward' - FORWARD_SECRETS = 'forward:secrets' PAYMENT_SESSIONS = 'payment-sessions' - PAYMENTS_SEARCH = 'payments:search' + PAYOUTS_BANK_DETAILS = 'payouts:bank-details' + REPORTS = 'reports' + REPORTS_VIEW = 'reports:view' + SESSIONS_APP = 'sessions:app' + SESSIONS_BROWSER = 'sessions:browser' + TRANSACTIONS = 'transactions' + TRANSFERS = 'transfers' + TRANSFERS_CREATE = 'transfers:create' + TRANSFERS_VIEW = 'transfers:view' + VAULT = 'vault' + VAULT_APME_ENROLLMENT = 'vault:apme-enrollment' + VAULT_CARD_METADATA = 'vault:card-metadata' + VAULT_CUSTOMERS = 'vault:customers' + VAULT_GPAYME_ENROLLMENT = 'vault:gpayme-enrollment' + VAULT_INSTRUMENTS = 'vault:instruments' + VAULT_NETWORK_TOKENS = 'vault:network-tokens' + VAULT_REAL_TIME_ACCOUNT_UPDATER = 'vault:real-time-account-updater' + VAULT_TOKENIZATION = 'vault:tokenization' diff --git a/checkout_sdk/paymentmethods/__init__.py b/checkout_sdk/paymentmethods/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/paymentmethods/payment_methods.py b/checkout_sdk/paymentmethods/payment_methods.py new file mode 100644 index 00000000..876527dd --- /dev/null +++ b/checkout_sdk/paymentmethods/payment_methods.py @@ -0,0 +1,2 @@ +class PaymentMethodsQueryFilter: + processing_channel_id: str diff --git a/checkout_sdk/paymentmethods/payment_methods_client.py b/checkout_sdk/paymentmethods/payment_methods_client.py new file mode 100644 index 00000000..c0e633f7 --- /dev/null +++ b/checkout_sdk/paymentmethods/payment_methods_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.paymentmethods.payment_methods import PaymentMethodsQueryFilter + + +class PaymentMethodsClient(Client): + __PAYMENT_METHODS_PATH = 'payment-methods' + + 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_available_payment_methods(self, processing_channel_id: str): + query = PaymentMethodsQueryFilter() + query.processing_channel_id = processing_channel_id + return self._api_client.get(self.__PAYMENT_METHODS_PATH, self._sdk_authorization(), query) diff --git a/checkout_sdk/payments/contexts/contexts.py b/checkout_sdk/payments/contexts/contexts.py index 63cbf8d9..0842ba24 100644 --- a/checkout_sdk/payments/contexts/contexts.py +++ b/checkout_sdk/payments/contexts/contexts.py @@ -57,6 +57,10 @@ class PaymentContextsProcessing: user_action: UserAction partner_customer_risk_data: list # payment.contexts.PaymentContextsPartnerCustomerRiskData airline_data: list # payment.contexts.PaymentContextsAirlineData + accommodation_data: list # AccommodationData + custom_payment_method_ids: list # list of str + discount_amount: int + tax_amount: int class PaymentContextsItems: @@ -87,6 +91,7 @@ class PaymentContextsRequest: success_url: str failure_url: str items: list # payments.contexts.PaymentContextsItems + metadata: dict @deprecated("This class will be removed in the future. Use PaymentContextPaypalSource instead") diff --git a/checkout_sdk/payments/hosted/hosted_payments.py b/checkout_sdk/payments/hosted/hosted_payments.py index 8ef895bb..c4f5ed3a 100644 --- a/checkout_sdk/payments/hosted/hosted_payments.py +++ b/checkout_sdk/payments/hosted/hosted_payments.py @@ -2,9 +2,10 @@ from checkout_sdk.common.common import CustomerRequest, CustomerRetry from checkout_sdk.common.enums import Currency -from checkout_sdk.payments.payments import BillingDescriptor, PaymentType, ShippingDetails, ThreeDsRequest, \ - RiskRequest, PaymentRecipient, ProcessingSettings, PaymentSender +from checkout_sdk.payments.payments import BillingDescriptor, PaymentInstruction, PaymentType, ShippingDetails, \ + ThreeDsRequest, RiskRequest, PaymentRecipient, ProcessingSettings, PaymentSender from checkout_sdk.payments.payments_previous import BillingInformation +from checkout_sdk.payments.sessions.sessions import SessionPaymentMethodConfiguration class HostedPaymentsSessionRequest: @@ -37,3 +38,5 @@ class HostedPaymentsSessionRequest: three_ds: ThreeDsRequest capture: bool capture_on: datetime + instruction: PaymentInstruction + payment_method_configuration: SessionPaymentMethodConfiguration diff --git a/checkout_sdk/payments/links/payments_links.py b/checkout_sdk/payments/links/payments_links.py index 24628fb0..c2a3623d 100644 --- a/checkout_sdk/payments/links/payments_links.py +++ b/checkout_sdk/payments/links/payments_links.py @@ -2,9 +2,10 @@ from checkout_sdk.common.common import CustomerRequest, CustomerRetry from checkout_sdk.common.enums import Currency -from checkout_sdk.payments.payments import BillingDescriptor, PaymentType, ShippingDetails, ThreeDsRequest, \ - RiskRequest, PaymentRecipient, ProcessingSettings, PaymentSender +from checkout_sdk.payments.payments import BillingDescriptor, PaymentInstruction, PaymentType, ShippingDetails, \ + ThreeDsRequest, RiskRequest, PaymentRecipient, ProcessingSettings, PaymentSender from checkout_sdk.payments.payments_previous import BillingInformation +from checkout_sdk.payments.sessions.sessions import SessionPaymentMethodConfiguration class PaymentLinkRequest: @@ -36,3 +37,5 @@ class PaymentLinkRequest: locale: str capture: bool capture_on: datetime + instruction: PaymentInstruction + payment_method_configuration: SessionPaymentMethodConfiguration diff --git a/checkout_sdk/payments/payment_apm.py b/checkout_sdk/payments/payment_apm.py index 1e475979..0f26c218 100644 --- a/checkout_sdk/payments/payment_apm.py +++ b/checkout_sdk/payments/payment_apm.py @@ -3,7 +3,7 @@ from datetime import datetime from checkout_sdk.common.common import Address, AccountHolder -from checkout_sdk.common.enums import PaymentSourceType, Country, Currency, AccountType +from checkout_sdk.common.enums import PaymentSourceType, Country, Currency, AccountType, SepaMandateType from checkout_sdk.payments.payments import PaymentRequestSource, BillingPlan, PaymentMethodDetails from checkout_sdk.tokens.tokens import ApplePayTokenData @@ -16,6 +16,7 @@ def __init__(self): super().__init__(PaymentSourceType.IDEAL) +# Deprecated: Sofort was removed from the Checkout.com API. This source no longer functions. class RequestSofortSource(PaymentRequestSource): countryCode: Country languageCode: str @@ -111,6 +112,7 @@ def __init__(self): super().__init__(PaymentSourceType.ILLICADO) +# Deprecated: Giropay was removed from the Checkout.com API. This source no longer functions. class RequestGiropaySource(PaymentRequestSource): account_holder: AccountHolder @@ -283,3 +285,60 @@ class RequestSequraSource(PaymentRequestSource): def __init__(self): super().__init__(PaymentSourceType.SEQURA) + + +class RequestMobilePaySource(PaymentRequestSource): + + def __init__(self): + super().__init__(PaymentSourceType.MOBILEPAY) + + +class RequestPayNowSource(PaymentRequestSource): + + def __init__(self): + super().__init__(PaymentSourceType.PAYNOW) + + +class SwishBillingDescriptor: + name: str + + +class RequestSwishSource(PaymentRequestSource): + payment_country: Country + account_holder: AccountHolder + billing_descriptor: SwishBillingDescriptor + + def __init__(self): + super().__init__(PaymentSourceType.SWISH) + + +class RequestTwintSource(PaymentRequestSource): + + def __init__(self): + super().__init__(PaymentSourceType.TWINT) + + +class RequestVippsSource(PaymentRequestSource): + + def __init__(self): + super().__init__(PaymentSourceType.VIPPS) + + +class RequestSepaV4Source(PaymentRequestSource): + country: Country + account_number: str + currency: Currency + mandate_id: str + mandate_type: SepaMandateType + date_of_signature: str + account_holder: AccountHolder + + def __init__(self): + super().__init__(PaymentSourceType.SEPA) + + +class RequestBlikSource(PaymentRequestSource): + partner_agreement_id: str + + def __init__(self): + super().__init__(PaymentSourceType.BLIK) diff --git a/checkout_sdk/payments/payments.py b/checkout_sdk/payments/payments.py index f0a1dfa7..a08fffdd 100644 --- a/checkout_sdk/payments/payments.py +++ b/checkout_sdk/payments/payments.py @@ -4,7 +4,7 @@ from enum import Enum from checkout_sdk.common.common import AccountHolder, BankDetails, MarketplaceData, Address, Phone, CustomerRequest, \ - AccountHolderIdentification + AccountHolderIdentification, QueryFilterDateRange from checkout_sdk.common.enums import PaymentSourceType, Currency, Country, AccountType, ChallengeIndicator from checkout_sdk.sessions.sessions import DeliveryTimeframe @@ -33,6 +33,15 @@ class PaymentSenderType(str, Enum): GOVERNMENT = 'government' +class SourceOfFunds(str, Enum): + CREDIT = 'credit' + DEBIT = 'debit' + PREPAID = 'prepaid' + DEPOSIT_ACCOUNT = 'deposit_account' + MOBILE_MONEY_ACCOUNT = 'mobile_money_account' + CASH = 'cash' + + class PayoutSourceType(str, Enum): CURRENCY_ACCOUNT = 'currency_account' ENTITY = 'entity' @@ -131,10 +140,60 @@ class PanPreference(str, Enum): DPAN = 'dpan' +class CardFundingType(str, Enum): + CREDIT = 'credit' + DEBIT = 'debit' + + +class ServiceType(str, Enum): + SAME_DAY = 'same_day' + STANDARD = 'standard' + + +class AmountVariability(str, Enum): + FIXED = 'Fixed' + VARIABLE = 'Variable' + + +class AuthenticationExperience(str, Enum): + GOOGLE_SPA = 'google_spa' + THREE_DS = '3ds' + + +class RoutingScheme(str, Enum): + ACCEL = 'accel' + AMEX = 'amex' + CARTES_BANCAIRES = 'cartes_bancaires' + DINERS = 'diners' + DISCOVER = 'discover' + JCB = 'jcb' + MADA = 'mada' + MAESTRO = 'maestro' + MASTERCARD = 'mastercard' + NYCE = 'nyce' + OMANNET = 'omannet' + PULSE = 'pulse' + SHAZAM = 'shazam' + STAR = 'star' + UPI = 'upi' + VISA = 'visa' + + +class LocalCharacterSets(str, Enum): + KANJI = 'kanji' + KATAKANA = 'katakana' + + +class LocalBillingDescriptor: + name: str + character_set: LocalCharacterSets + + class BillingDescriptor: name: str city: str reference: str + local_descriptors: list # LocalBillingDescriptor class Remitance: @@ -148,6 +207,7 @@ class PaymentInstruction: scheme: InstructionScheme remittance: Remitance quote_id: str + funds_transfer_type: str class PayoutBillingDescriptor: @@ -157,6 +217,7 @@ class PayoutBillingDescriptor: # Payment Sender class PaymentSender: type: PaymentSenderType + reference: str def __init__(self, type_p: PaymentSenderType): self.type = type_p @@ -167,25 +228,38 @@ class PaymentCorporateSender(PaymentSender): address: Address reference: str reference_type: str - source_of_funds: str + source_of_funds: SourceOfFunds identification: AccountHolderIdentification def __init__(self): super().__init__(PaymentSenderType.CORPORATE) -class PaymentGovermentSender(PaymentSender): +# Narrow-applicability sender. The `government` type is only accepted by the +# CardPayoutRequest.sender slot in the API. Using this class on +# PaymentRequest.sender or BankPayoutRequest.sender will be rejected by the +# API with a 422, because those endpoints' sender discriminators only accept +# `individual`, `corporate`, and `instrument`. Type safety can't catch this +# today because Python's PaymentSender is shared across all three slots; a +# future split into a dedicated CardPayoutSender hierarchy would fix it. +class PaymentGovernmentSender(PaymentSender): company_name: str address: Address reference: str reference_type: str - source_of_funds: str + source_of_funds: SourceOfFunds identification: AccountHolderIdentification def __init__(self): super().__init__(PaymentSenderType.GOVERNMENT) +# Backward-compat alias for the misspelled class name shipped in earlier SDK +# versions. Prefer PaymentGovernmentSender in new code; this alias will be +# removed in a future major version. +PaymentGovermentSender = PaymentGovernmentSender + + class PaymentIndividualSender(PaymentSender): first_name: str middle_name: str @@ -195,7 +269,7 @@ class PaymentIndividualSender(PaymentSender): identification: AccountHolderIdentification reference: str reference_type: str - source_of_funds: str + source_of_funds: SourceOfFunds date_of_birth: str country_of_birth: Country nationality: Country @@ -229,6 +303,7 @@ class PaymentRequestCardSource(PaymentRequestSource): billing_address: Address phone: Phone account_holder: AccountHolder + allow_update: bool def __init__(self): super().__init__(PaymentSourceType.CARD) @@ -272,6 +347,9 @@ class PaymentRequestIdSource(PaymentRequestSource): stored: bool store_for_future_use: bool account_holder: AccountHolder + billing_address: Address + phone: Phone + allow_update: bool def __init__(self): super().__init__(PaymentSourceType.ID) @@ -301,6 +379,9 @@ def __init__(self): class RequestCustomerSource(PaymentRequestSource): id: str account_holder: AccountHolder + billing_address: Address + phone: Phone + allow_update: bool def __init__(self): super().__init__(PaymentSourceType.CUSTOMER) @@ -313,6 +394,15 @@ class PaymentContextsShippingMethod(str, Enum): OTHER_ADDRESS = 'OtherAddress' +class TrackingInfo: + tracking_number: str + tracking_uri: str + shipping_company: str + return_tracking_number: str + return_tracking_uri: str + return_shipping_company: str + + class ShippingDetails: first_name: str last_name: str @@ -323,6 +413,15 @@ class ShippingDetails: timeframe: DeliveryTimeframe method: PaymentContextsShippingMethod delay: int + tracking_info: list # TrackingInfo + + +class InitialAuthentication: + acs_transaction_id: str + authentication_method: str + authentication_timestamp: str + authentication_data: str + initial_session_id: str class ThreeDsRequest: @@ -344,11 +443,47 @@ class ThreeDsRequest: score: str cryptogram_algorithm: str authentication_id: str + initial_authentication: InitialAuthentication + + +class DeviceProvider: + id: str + name: str + + +class Network: + ipv4: str + ipv6: str + tor: bool + vpn: bool + proxy: bool + + +class DeviceDetails: + user_agent: str + network: Network + provider: DeviceProvider + timestamp: str + timezone: str + virtual_machine: bool + incognito: bool + jailbroken: bool + rooted: bool + java_enabled: bool + javascript_enabled: bool + language: str + color_depth: str + screen_height: str + screen_width: str + user_agent_client_hint: str + iframe_payment_allowed: bool + accept_header: str class RiskRequest: enabled: bool device_session_id: str + device: DeviceDetails class PaymentRecipient: @@ -377,17 +512,22 @@ class DLocalProcessingSettings: installments: Installments +# Deprecated: SenderInformation is not defined in the current Checkout.com API +# (NAS) swagger and no documented endpoint accepts a `sender_information` field +# on ProcessingSettings. Retained for backward compatibility with previous-API +# (ABC) callers; new code should not set this. Will be removed in a future +# major version. class SenderInformation: reference: str - firstName: str - lastName: str + first_name: str + last_name: str dob: str address: str city: str state: str country: str - postalCode: str - sourceOfFunds: str + postal_code: str + source_of_funds: str class PartnerCustomerRiskData: @@ -425,6 +565,12 @@ class AccommodationData: room: list # AccommodationRoom +class Aggregator: + sub_merchant_id: str + aggregator_id_visa: str + aggregator_id_mc: str + + class ProcessingSettings: order_id: str tax_amount: int @@ -457,7 +603,8 @@ class ProcessingSettings: shipping_delay: int shipping_info: list # ShippingInfo dlocal: DLocalProcessingSettings - senderInformation: SenderInformation + # Deprecated: see SenderInformation class — no current API endpoint reads this. + sender_information: SenderInformation purpose: str partner_customer_risk_data: list # PartnerCustomerRiskData accommodation_data: list # AccommodationData @@ -466,6 +613,13 @@ class ProcessingSettings: provision_network_token: bool affiliate_id: str affiliate_url: str + aggregator: Aggregator + card_type: CardFundingType + foreign_retailer_amount: int + reconciliation_id: str + service_type: ServiceType + partner_code: str + processing_speed: str # 'fast' (only for unreferenced refunds / card payouts) class ProductSubType (str, Enum): @@ -504,10 +658,22 @@ class PaymentSegment: market: str +class DowntimeRetryRequest: + enabled: bool + + +class DunningRetryRequest: + enabled: bool + max_attempts: int + end_after_days: int + + class PaymentRetryRequest: enabled: bool max_attempts: int end_after_days: int + downtime: DowntimeRetryRequest + dunning: DunningRetryRequest # Request Payment @@ -515,9 +681,45 @@ class PartialAuthorization: enabled: bool +class PaymentAuthenticationRequest: + preferred_experiences: list # AuthenticationExperience + + +class PaymentRoutingAttempt: + scheme: RoutingScheme + + +class PaymentRouting: + attempts: list # PaymentRoutingAttempt + + +class PaymentSubscription: + id: str + + +class PaymentPlan: + days_between_payments: int + total_number_of_payments: int + current_payment_number: int + expiry: str + amount: int + name: str + start_date: str + + +class PlanInstallment(PaymentPlan): + financing: bool + amount: str + + +class PlanRecurring(PaymentPlan): + amount_variability: AmountVariability + + class PaymentRequest: payment_context_id: str source: PaymentRequestSource + fallback_source: PaymentRequestSource amount: int currency: Currency payment_type: PaymentType @@ -528,11 +730,13 @@ class PaymentRequest: partial_authorization: PartialAuthorization capture: bool capture_on: datetime + expire_on: datetime customer: PaymentCustomerRequest billing_descriptor: BillingDescriptor shipping: ShippingDetails segment: PaymentSegment three_ds: ThreeDsRequest + authentication: PaymentAuthenticationRequest processing_channel_id: str previous_payment_id: str risk: RiskRequest @@ -549,6 +753,9 @@ class PaymentRequest: items: list # payments.Product retry: PaymentRetryRequest instruction: PaymentInstruction + payment_plan: PaymentPlan + routing: PaymentRouting + subscription: PaymentSubscription # Payout Request Source @@ -585,6 +792,7 @@ class PaymentBankAccountDestination(PaymentRequestDestination): account_type: AccountType account_number: str bank_code: str + bban: str branch_code: str iban: str swift_bic: str @@ -616,6 +824,11 @@ class PayoutRequest: sender: PaymentSender instruction: PaymentInstruction processing_channel_id: str + segment: PaymentSegment + items: list # payments.Product + metadata: dict + previous_payment_id: str + processing: ProcessingSettings # Query @@ -656,6 +869,9 @@ class RefundRequest: metadata: dict # Not available on Previous amount_allocations: list # values of AmountAllocations + capture_action_id: str + destination: PaymentBankAccountDestination + items: list # payments.Product # Voids @@ -664,6 +880,23 @@ class VoidRequest: metadata: dict +# Cancellations +class CancelScheduledRetryRequest: + reference: str + + +# Reversals +class ReversePaymentRequest: + reference: str + metadata: dict + + +# Search +class PaymentsSearchRequest(QueryFilterDateRange): + query: str + limit: int + + class BillingPlan: type: BillingPlanType skip_shipping_address: bool diff --git a/checkout_sdk/payments/payments_apm_previous.py b/checkout_sdk/payments/payments_apm_previous.py index cacb90c3..69b95f96 100644 --- a/checkout_sdk/payments/payments_apm_previous.py +++ b/checkout_sdk/payments/payments_apm_previous.py @@ -85,6 +85,7 @@ def __init__(self): super().__init__(PaymentSourceType.FAWRY) +# Deprecated: Giropay was removed from the Checkout.com API. This source no longer functions. class RequestGiropaySource(RequestSource): purpose: str bic: str @@ -189,6 +190,7 @@ def __init__(self): super().__init__(PaymentSourceType.PAYPAL) +# Deprecated: POLi was removed from the Checkout.com API. This source no longer functions. class RequestPoliSource(RequestSource): def __init__(self): super().__init__(PaymentSourceType.POLI) @@ -221,6 +223,7 @@ def __init__(self): super().__init__(PaymentSourceType.ID) +# Deprecated: Sofort was removed from the Checkout.com API. This source no longer functions. class RequestSofortSource(RequestSource): countryCode: Country languageCode: str diff --git a/checkout_sdk/payments/payments_client.py b/checkout_sdk/payments/payments_client.py index 528b8846..6b03e49b 100644 --- a/checkout_sdk/payments/payments_client.py +++ b/checkout_sdk/payments/payments_client.py @@ -5,7 +5,8 @@ from checkout_sdk.checkout_configuration import CheckoutConfiguration from checkout_sdk.client import Client from checkout_sdk.payments.payments import PaymentRequest, PayoutRequest, CaptureRequest, AuthorizationRequest, \ - RefundRequest, VoidRequest, PaymentsQueryFilter + RefundRequest, VoidRequest, PaymentsQueryFilter, CancelScheduledRetryRequest, ReversePaymentRequest, \ + PaymentsSearchRequest class PaymentsClient(Client): @@ -32,6 +33,12 @@ def get_payment_actions(self, payment_id: str): return self._api_client.get(self.build_path(self.__PAYMENTS_PATH, payment_id, 'actions'), self._sdk_authorization()) + def cancel_scheduled_retry(self, payment_id: str, + cancel_scheduled_retry_request: CancelScheduledRetryRequest, + idempotency_key: str = None): + return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, payment_id, 'cancellations'), + self._sdk_authorization(), cancel_scheduled_retry_request, idempotency_key) + def capture_payment(self, payment_id: str, capture_request: CaptureRequest = None, idempotency_key: str = None): return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, payment_id, 'captures'), self._sdk_authorization(), capture_request, idempotency_key) @@ -40,6 +47,11 @@ def refund_payment(self, payment_id: str, refund_request: RefundRequest = None, return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, payment_id, 'refunds'), self._sdk_authorization(), refund_request, idempotency_key) + def reverse_payment(self, payment_id: str, reverse_payment_request: ReversePaymentRequest = None, + idempotency_key: str = None): + return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, payment_id, 'reversals'), + self._sdk_authorization(), reverse_payment_request, idempotency_key) + def void_payment(self, payment_id: str, void_request: VoidRequest = None, idempotency_key: str = None): return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, payment_id, 'voids'), self._sdk_authorization(), void_request, idempotency_key) @@ -48,3 +60,7 @@ def increment_payment_authorization(self, payment_id: str, authorization_request idempotency_key: str = None): return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, payment_id, 'authorizations'), self._sdk_authorization(), authorization_request, idempotency_key) + + def search_payments(self, search_request: PaymentsSearchRequest): + return self._api_client.post(self.build_path(self.__PAYMENTS_PATH, 'search'), + self._sdk_authorization(), search_request) diff --git a/checkout_sdk/payments/sessions/sessions.py b/checkout_sdk/payments/sessions/sessions.py index a4c5f47b..7961c516 100644 --- a/checkout_sdk/payments/sessions/sessions.py +++ b/checkout_sdk/payments/sessions/sessions.py @@ -4,6 +4,7 @@ from checkout_sdk.common.enums import Currency from checkout_sdk.payments.payments import PaymentType, BillingDescriptor, ShippingDetails, \ PaymentRecipient, ProcessingSettings, RiskRequest, ThreeDsRequest, PaymentSender +from checkout_sdk.sessions.sessions import SessionsBillingDescriptor class PaymentMethodsType(str, Enum): @@ -286,8 +287,24 @@ class PaymentSessionWithPaymentRequest: class SubmitPaymentSessionRequest: session_data: str amount: int + currency: Currency reference: str + description: str items: list # Item three_ds: ThreeDsRequest ip_address: str payment_type: PaymentType + billing: SessionBilling + billing_descriptor: SessionsBillingDescriptor + capture: bool + capture_on: datetime + customer: SessionPaymentCustomerRequest + failure_url: str + instruction: Instruction + metadata: dict + payment_method_configuration: SessionPaymentMethodConfiguration + processing_channel_id: str + recipient: PaymentRecipient + sender: PaymentSender + shipping: ShippingDetails + success_url: str diff --git a/checkout_sdk/payments/setups/setups.py b/checkout_sdk/payments/setups/setups.py index 935faeea..a06b525a 100644 --- a/checkout_sdk/payments/setups/setups.py +++ b/checkout_sdk/payments/setups/setups.py @@ -41,6 +41,9 @@ class Customer: phone: Phone device: CustomerDevice merchant_account: MerchantAccount + country: str + id: str + tax_number: str # Payment Method Common entities @@ -91,6 +94,8 @@ def __init__(self): # Tabby entities class Tabby(PaymentMethodBase): + payment_types: list # list of str + def __init__(self): super().__init__() self.payment_method_options: PaymentMethodOptions @@ -114,6 +119,8 @@ class PaymentMethods: class Settings: success_url: str failure_url: str + capture: bool + excluded_payment_methods: list # list of str (PaymentSourceType values) # Order entities @@ -121,6 +128,7 @@ class OrderSubMerchant: id: str product_category: str number_of_trades: int + number_of_sales: int registration_date: datetime @@ -129,6 +137,9 @@ class Order: shipping: ShippingDetails sub_merchants: list # list of OrderSubMerchant discount_amount: int + invoice_id: str + shipping_amount: int + tax_amount: int # Industry entities @@ -143,6 +154,11 @@ class Industry: accommodation_data: list # list of AccommodationData +# Billing entity +class PaymentSetupBilling: + address: Address + + # Main Request and Response classes class PaymentSetupsRequest: processing_channel_id: str @@ -156,3 +172,4 @@ class PaymentSetupsRequest: customer: Customer order: Order industry: Industry + billing: PaymentSetupBilling diff --git a/checkout_sdk/sessions/sessions.py b/checkout_sdk/sessions/sessions.py index 9d0e441b..15a33dde 100644 --- a/checkout_sdk/sessions/sessions.py +++ b/checkout_sdk/sessions/sessions.py @@ -2,7 +2,7 @@ from enum import Enum from checkout_sdk.common.common import Phone, Address -from checkout_sdk.common.enums import Currency, ChallengeIndicator, CardholderAccountAgeIndicatorType, \ +from checkout_sdk.common.enums import Currency, CardholderAccountAgeIndicatorType, \ AccountChangeIndicatorType, AccountPasswordChangeIndicatorType, AccountTypeCardProductType @@ -46,7 +46,22 @@ class AuthenticationType(str, Enum): class Category(str, Enum): PAYMENT = 'payment' - NON_PAYMENT = 'nonPayment' + NON_PAYMENT = 'non_payment' + + +# Wider variant of common.enums.ChallengeIndicator. Only used by SessionRequest +# (the /sessions 3DS endpoint), which folds exemption requests into this field +# instead of having a separate `exemption` field like ThreeDsRequest does. +class SessionChallengeIndicator(str, Enum): + NO_PREFERENCE = 'no_preference' + NO_CHALLENGE_REQUESTED = 'no_challenge_requested' + CHALLENGE_REQUESTED = 'challenge_requested' + CHALLENGE_REQUESTED_MANDATE = 'challenge_requested_mandate' + LOW_VALUE = 'low_value' + TRUSTED_LISTING = 'trusted_listing' + TRUSTED_LISTING_PROMPT = 'trusted_listing_prompt' + TRANSACTION_RISK_ASSESSMENT = 'transaction_risk_assessment' + DATA_SHARE = 'data_share' class TransactionType(str, Enum): @@ -54,7 +69,7 @@ class TransactionType(str, Enum): CHECK_ACCEPTANCE = 'check_acceptance' GOODS_SERVICE = 'goods_service' PREPAID_ACTIVATION_AND_LOAD = 'prepaid_activation_and_load' - QUASHI_CARD_TRANSACTION = 'quashi_card_transaction' + QUASI_CARD_TRANSACTION = 'quasi_card_transaction' class UIElements(str, Enum): @@ -111,6 +126,8 @@ class SessionMarketplaceData: class SessionsBillingDescriptor: name: str + city: str + reference: str # Channel @@ -346,6 +363,15 @@ class InitialTransaction: initial_session_id: str +class DeviceInformation: + device_id: str + device_session_id: str + + +class GoogleSpa: + continue_url: str + + class SessionRequest: source: SessionSource = SessionCardSource() amount: int @@ -355,7 +381,7 @@ class SessionRequest: authentication_type: AuthenticationType = AuthenticationType.REGULAR authentication_category: Category = Category.PAYMENT account_info: CardholderAccountInfo - challenge_indicator: ChallengeIndicator = ChallengeIndicator.NO_PREFERENCE + challenge_indicator: SessionChallengeIndicator = SessionChallengeIndicator.NO_PREFERENCE billing_descriptor: SessionsBillingDescriptor reference: str merchant_risk_info: MerchantRiskInfo @@ -369,6 +395,9 @@ class SessionRequest: installment: Installment optimization: Optimization initial_transaction: InitialTransaction + device_information: DeviceInformation + google_spa: GoogleSpa + preferred_experiences: list # AuthenticationExperience ('3ds' | 'google_spa') class ThreeDsMethodCompletionRequest: diff --git a/checkout_sdk/tokens/tokens.py b/checkout_sdk/tokens/tokens.py index 67d1521b..7d2093bd 100644 --- a/checkout_sdk/tokens/tokens.py +++ b/checkout_sdk/tokens/tokens.py @@ -18,6 +18,7 @@ class CardTokenRequest: expiry_year: int name: str cvv: str + pin: str billing_address: Address phone: Phone diff --git a/tests/_assertions.py b/tests/_assertions.py new file mode 100644 index 00000000..5c96a883 --- /dev/null +++ b/tests/_assertions.py @@ -0,0 +1,33 @@ +"""Shared assertions for client tests. + +Use these instead of the trivial pattern: + + mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.create_card(PhysicalCardRequest()) == 'response' + +which only verifies the mock-passthrough — not the path, body, or HTTP verb. +""" + + +def assert_api_call(mock, path, body=None): + """Verify a mocked ApiClient.{get,post,put,patch,delete} call. + + Args: + mock: the MagicMock returned by mocker.patch('checkout_sdk.api_client.ApiClient.', ...) + path: the expected URL path (e.g. 'issuing/cards/card_id'). Compared by ==. + body: optional — the expected request body object. Compared by `is` + (identity) so a typo or accidental object swap is caught immediately. + + The HTTP verb is verified implicitly: if the client uses the wrong verb, + the patched mock is never invoked and `assert_called_once()` will fail. + + Positional args on ApiClient.post/put/patch are (path, authorization, body, ...); + ApiClient.get/delete pass (path, authorization, [params]). For verbs without a + body, omit `body=` from the call site. + """ + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == path, f'path expected={path!r} got={args[0]!r}' + if body is not None: + assert len(args) >= 3, f'no body in call: {args!r}' + assert args[2] is body, f'body mismatch: expected={body!r} got={args[2]!r}' diff --git a/tests/accounts/accounts_client_test.py b/tests/accounts/accounts_client_test.py index 85a0ab65..92413bc2 100644 --- a/tests/accounts/accounts_client_test.py +++ b/tests/accounts/accounts_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.accounts.accounts import OnboardEntityRequest, AccountsPaymentInstrument, UpdateScheduleRequest, \ PaymentInstrumentRequest, PaymentInstrumentsQuery, UpdatePaymentInstrumentRequest, ReserveRuleRequest, \ EntityFileRequest, FilePurpose @@ -18,80 +19,133 @@ def client(mock_sdk_configuration, mock_api_client): class TestAccountsClient: def test_should_create_entity(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_entity(OnboardEntityRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = OnboardEntityRequest() + + assert client.create_entity(body) == 'response' + assert_api_call(mock, 'accounts/entities', body) def test_should_get_entity(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_entity('entity_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id') def test_should_update_entity(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_entity('entity_id', OnboardEntityRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = OnboardEntityRequest() + + assert client.update_entity('entity_id', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id', body) def test_should_create_payment_instrument_deprecated(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_payment_instrument('entity_id', AccountsPaymentInstrument()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = AccountsPaymentInstrument() + + assert client.create_payment_instrument('entity_id', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/instruments', body) def test_should_create_payment_instrument(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.add_payment_instrument('entity_id', PaymentInstrumentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentInstrumentRequest() + + assert client.add_payment_instrument('entity_id', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/payment-instruments', body) def test_should_update_payment_instrument(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update_payment_instrument('entity_id', 'instrument_id', - UpdatePaymentInstrumentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdatePaymentInstrumentRequest() + + assert client.update_payment_instrument('entity_id', 'instrument_id', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/payment-instruments/instrument_id', body) def test_should_query_payment_instruments(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.query_payment_instruments('entity_id', PaymentInstrumentsQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + body = PaymentInstrumentsQuery() + + assert client.query_payment_instruments('entity_id', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/payment-instruments', body) def test_should_retrieve_payment_instrument_details(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_payment_instrument_details('entity_id', 'payment_instrument_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/payment-instruments/payment_instrument_id') def test_should_upload_file(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.submit_file', return_value='response') - assert client.upload_file(FileRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.submit_file', return_value='response') + body = FileRequest() + + assert client.upload_file(body) == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'files' + assert args[2] is body def test_should_update_payout_schedule(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_payout_schedule('entity_id', Currency.USD, UpdateScheduleRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = UpdateScheduleRequest() + + assert client.update_payout_schedule('entity_id', Currency.USD, body) == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'accounts/entities/entity_id/payout-schedules' + assert args[2] == {Currency.USD: body} def test_should_retrieve_payout_schedule(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_payout_schedule('entity_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/payout-schedules') def test_should_get_sub_entity_members(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_sub_entity_members('entity_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/members') def test_should_reinvite_sub_entity_member(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + assert client.reinvite_sub_entity_member('entity_id', 'user_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/members/user_id') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ReserveRuleRequest() + + assert client.create_reserve_rule('entity_id', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/reserve-rules', body) def test_should_get_reserve_rules(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_reserve_rules('entity_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/reserve-rules') def test_should_get_reserve_rule_details(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_reserve_rule_details('entity_id', 'reserve_rule_id') == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/reserve-rules/reserve_rule_id') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = ReserveRuleRequest() + + assert client.update_reserve_rule('entity_id', 'reserve_rule_id', 'etag_value', body) == 'response' + assert_api_call(mock, 'accounts/entities/entity_id/reserve-rules/reserve_rule_id', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = EntityFileRequest() + body.purpose = FilePurpose.IDENTIFICATION + + assert client.upload_entity_file('entity_id', body) == 'response' + assert_api_call(mock, 'entities/entity_id/files', body) def test_should_retrieve_entity_file(self, mocker, client: AccountsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_entity_file('entity_id', 'file_id') == 'response' + assert_api_call(mock, 'entities/entity_id/files/file_id') diff --git a/tests/agenticcommerce/agentic_commerce_client_test.py b/tests/agenticcommerce/agentic_commerce_client_test.py index d7da2083..2f70bbf1 100644 --- a/tests/agenticcommerce/agentic_commerce_client_test.py +++ b/tests/agenticcommerce/agentic_commerce_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.agenticcommerce.agentic_commerce_client import AgenticCommerceClient from checkout_sdk.agenticcommerce.agentic_commerce import DelegatedPaymentRequest, DelegatedPaymentHeaders @@ -12,43 +13,27 @@ def client(mock_sdk_configuration, mock_api_client): 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() + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = DelegatedPaymentRequest() headers = DelegatedPaymentHeaders() - response = client.create_delegated_payment_token(request, headers) - - assert response == 'response' + assert client.create_delegated_payment_token(body, headers) == 'response' + assert_api_call(mock, 'agentic_commerce/delegate_payment', body) + # Verify headers are forwarded as a kwarg (not part of positional args) + assert mock.call_args.kwargs.get('headers') is headers 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() + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - 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 + assert client.create_delegated_payment_token(None, DelegatedPaymentHeaders()) == 'response' + # Body=None — verify path only; positional args[2] is the None body. + assert_api_call(mock, 'agentic_commerce/delegate_payment') + assert mock.call_args.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 = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = DelegatedPaymentRequest() - mock_post.assert_called_once() - args = mock_post.call_args[0] - assert 'agentic_commerce/delegate_payment' in args[0] + assert client.create_delegated_payment_token(body, None) == 'response' + assert_api_call(mock, 'agentic_commerce/delegate_payment', body) + assert mock.call_args.kwargs.get('headers') is None diff --git a/tests/apm/ideal_client_test.py b/tests/apm/ideal_client_test.py index 936423ce..b72d2054 100644 --- a/tests/apm/ideal_client_test.py +++ b/tests/apm/ideal_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.apm.ideal_client import IdealClient @@ -11,9 +12,13 @@ def client(mock_sdk_configuration, mock_api_client): class TestIdealClient: def test_should_get_info(self, mocker, client: IdealClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_info() == 'response' + assert_api_call(mock, 'ideal-external') def test_get_issuers(self, mocker, client: IdealClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_issuers() == 'response' + assert_api_call(mock, 'ideal-external/issuers') diff --git a/tests/apm/klarna_client_test.py b/tests/apm/klarna_client_test.py index 0e44c294..8217adf2 100644 --- a/tests/apm/klarna_client_test.py +++ b/tests/apm/klarna_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.apm.klarna import CreditSessionRequest, OrderCaptureRequest from checkout_sdk.apm.klarna_client import KlarnaClient from checkout_sdk.payments.payments import VoidRequest @@ -10,20 +11,32 @@ def client(mock_sdk_configuration, mock_api_client): return KlarnaClient(api_client=mock_api_client, configuration=mock_sdk_configuration) +# Sandbox mock configuration → klarna_client uses 'klarna-external' base path. class TestKlarnaClient: def test_should_create_credit_session(self, mocker, client: KlarnaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_credit_session(CreditSessionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreditSessionRequest() + + assert client.create_credit_session(body) == 'response' + assert_api_call(mock, 'klarna-external/credit-sessions', body) def test_should_get_credit_session(self, mocker, client: KlarnaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_credit_session('session_id') == 'response' + assert_api_call(mock, 'klarna-external/credit-sessions/session_id') def test_capture_payment(self, mocker, client: KlarnaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.capture_payment('payment_id', OrderCaptureRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = OrderCaptureRequest() + + assert client.capture_payment('payment_id', body) == 'response' + assert_api_call(mock, 'klarna-external/orders/payment_id/captures', body) def test_void_payment(self, mocker, client: KlarnaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.void_payment('payment_id', VoidRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = VoidRequest() + + assert client.void_payment('payment_id', body) == 'response' + assert_api_call(mock, 'klarna-external/orders/payment_id/voids', body) diff --git a/tests/apm/sepa_client_test.py b/tests/apm/sepa_client_test.py index 6762e11d..4d7f23ea 100644 --- a/tests/apm/sepa_client_test.py +++ b/tests/apm/sepa_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.apm.sepa_client import SepaClient @@ -11,17 +12,25 @@ def client(mock_sdk_configuration, mock_api_client): class TestSepaClient: def test_should_get_mandate(self, mocker, client: SepaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_mandate('mandate_id') == 'response' + assert_api_call(mock, 'sepa/mandates/mandate_id') def test_should_cancel_mandate(self, mocker, client: SepaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.cancel_mandate('mandate_id') == 'response' + assert_api_call(mock, 'sepa/mandates/mandate_id/cancel') def test_should_get_mandate_via_ppro(self, mocker, client: SepaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_mandate_via_ppro('mandate_id') == 'response' + assert_api_call(mock, 'apms/ppro/sepa/mandates/mandate_id') def test_should_cancel_mandate_via_ppro(self, mocker, client: SepaClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.cancel_mandate_via_ppro('payment_id') == 'response' + assert_api_call(mock, 'apms/ppro/sepa/mandates/payment_id/cancel') diff --git a/tests/balances/balances_client_test.py b/tests/balances/balances_client_test.py index c85a47b3..bc6cba70 100644 --- a/tests/balances/balances_client_test.py +++ b/tests/balances/balances_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.balances.balances import BalancesQuery from checkout_sdk.balances.balances_client import BalancesClient @@ -12,5 +13,8 @@ def client(mock_sdk_configuration, mock_api_client): class TestBalancesClient: def test_should_retrieve_entity_balances(self, mocker, client: BalancesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.retrieve_entity_balances('entity_id', BalancesQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = BalancesQuery() + + assert client.retrieve_entity_balances('entity_id', query) == 'response' + assert_api_call(mock, 'balances/entity_id', query) diff --git a/tests/compliancerequests/compliance_requests_client_test.py b/tests/compliancerequests/compliance_requests_client_test.py index b00d2ca7..75bd9c8d 100644 --- a/tests/compliancerequests/compliance_requests_client_test.py +++ b/tests/compliancerequests/compliance_requests_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.compliancerequests.compliance_requests_client import ComplianceRequestsClient from checkout_sdk.compliancerequests.compliance_requests import ComplianceRequestRespondRequest @@ -12,15 +13,10 @@ def client(mock_sdk_configuration, mock_api_client): 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" + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - 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] + assert client.get_compliance_request('pay_fun26akvvjjerahhctaq2uzhu4') == 'response' + assert_api_call(mock, 'compliance-requests/pay_fun26akvvjjerahhctaq2uzhu4') def test_get_compliance_request_with_none_payment_id(self, mocker, client: ComplianceRequestsClient): mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') @@ -29,29 +25,21 @@ def test_get_compliance_request_with_none_payment_id(self, mocker, client: Compl 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) + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ComplianceRequestRespondRequest() - 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 + assert client.respond_to_compliance_request('pay_fun26akvvjjerahhctaq2uzhu4', body) == 'response' + assert_api_call(mock, 'compliance-requests/pay_fun26akvvjjerahhctaq2uzhu4', body) 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) + client.respond_to_compliance_request(None, ComplianceRequestRespondRequest()) 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) + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert response == 'response' + assert client.respond_to_compliance_request('pay_fun26akvvjjerahhctaq2uzhu4', None) == 'response' + # Body is None — verify path; helper's body=None skips body identity check. + assert_api_call(mock, 'compliance-requests/pay_fun26akvvjjerahhctaq2uzhu4') diff --git a/tests/conftest.py b/tests/conftest.py index 4405a7b1..fd0953f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -49,7 +49,8 @@ def oauth_api(): 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_REAL_TIME_ACCOUNT_UPDATER]) \ + OAuthScopes.VAULT_REAL_TIME_ACCOUNT_UPDATER, OAuthScopes.PAYMENTS_SEARCH, + OAuthScopes.GATEWAY_PAYMENT_CANCELLATIONS]) \ .build() diff --git a/tests/customers/customers_client_test.py b/tests/customers/customers_client_test.py index f3b4fea8..41c20db8 100644 --- a/tests/customers/customers_client_test.py +++ b/tests/customers/customers_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.customers.customers_client import CustomersClient from checkout_sdk.customers.customers import CustomerRequest @@ -12,17 +13,27 @@ def client(mock_sdk_configuration, mock_api_client): class TestCustomersClient: def test_should_get_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get('customer_id') == 'response' + assert_api_call(mock, 'customers/customer_id') def test_should_create_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create(CustomerRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CustomerRequest() + + assert client.create(body) == 'response' + assert_api_call(mock, 'customers', body) def test_should_update_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update('customer_id', CustomerRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = CustomerRequest() + + assert client.update('customer_id', body) == 'response' + assert_api_call(mock, 'customers/customer_id', body) def test_should_delete_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.delete('customer_id') == 'response' + assert_api_call(mock, 'customers/customer_id') diff --git a/tests/customers/customers_previous_client_test.py b/tests/customers/customers_previous_client_test.py index abd47f17..710694e9 100644 --- a/tests/customers/customers_previous_client_test.py +++ b/tests/customers/customers_previous_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.customers.customers import CustomerRequest from checkout_sdk.customers.customers_client_previous import CustomersClient @@ -12,17 +13,27 @@ def client(mock_sdk_configuration, mock_api_client): class TestCustomersClient: def test_should_get_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get('customer_id') == 'response' + assert_api_call(mock, 'customers/customer_id') def test_should_create_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create(CustomerRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CustomerRequest() + + assert client.create(body) == 'response' + assert_api_call(mock, 'customers', body) def test_should_update_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update('customer_id', CustomerRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = CustomerRequest() + + assert client.update('customer_id', body) == 'response' + assert_api_call(mock, 'customers/customer_id', body) def test_should_delete_customer(self, mocker, client: CustomersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.delete('customer_id') == 'response' + assert_api_call(mock, 'customers/customer_id') diff --git a/tests/disputes/disputes_client_test.py b/tests/disputes/disputes_client_test.py index a40a57d2..c151044b 100644 --- a/tests/disputes/disputes_client_test.py +++ b/tests/disputes/disputes_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.disputes.disputes import DisputesQueryFilter, DisputeEvidenceRequest from checkout_sdk.disputes.disputes_client import DisputesClient from checkout_sdk.files.files import FileRequest @@ -13,41 +14,79 @@ def client(mock_sdk_configuration, mock_api_client): class TestDisputesClient: def test_should_query_dispute(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.query(DisputesQueryFilter()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + body = DisputesQueryFilter() + + assert client.query(body) == 'response' + assert_api_call(mock, 'disputes', body) def test_should_get_dispute_details(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_dispute_details('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id') def test_should_accept_dispute(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.accept('customer_id') == 'response' + assert_api_call(mock, 'disputes/customer_id/accept') def test_should_put_evidence(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.put_evidence('dispute_id', DisputeEvidenceRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = DisputeEvidenceRequest() + + assert client.put_evidence('dispute_id', body) == 'response' + assert_api_call(mock, 'disputes/dispute_id/evidence', body) def test_should_get_evidence(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_evidence('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id/evidence') def test_should_submit_evidence(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.submit_evidence('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id/evidence') + + def test_should_submit_arbitration_evidence(self, mocker, client: DisputesClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.submit_arbitration_evidence('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id/evidence/arbitration') def test_should_get_compiled_submitted_evidence(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_compiled_submitted_evidence('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id/evidence/submitted') + + def test_should_get_compiled_submitted_arbitration_evidence(self, mocker, client: DisputesClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_compiled_submitted_arbitration_evidence('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id/evidence/arbitration/submitted') def test_should_upload_file(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.submit_file', return_value='response') - assert client.upload_file(FileRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.submit_file', return_value='response') + body = FileRequest() + + assert client.upload_file(body) == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'files' + assert args[2] is body def test_should_get_file_details(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_file_details('file_id') == 'response' + assert_api_call(mock, 'files/file_id') def test_should_get_dispute_scheme_files(self, mocker, client: DisputesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_dispute_scheme_files('dispute_id') == 'response' + assert_api_call(mock, 'disputes/dispute_id/schemefiles') diff --git a/tests/disputes/disputes_integration_test.py b/tests/disputes/disputes_integration_test.py index 788d9c48..215d1af1 100644 --- a/tests/disputes/disputes_integration_test.py +++ b/tests/disputes/disputes_integration_test.py @@ -12,6 +12,9 @@ from tests.payments.previous.payments_previous_test_utils import make_card_payment +PLACEHOLDER_DISPUTE_ID = 'dsp_test_placeholder' + + def test_should_query_disputes(default_api): query = DisputesQueryFilter() now = datetime.now(timezone.utc) @@ -123,3 +126,29 @@ def test_should_disputes_scheme_files(default_api): assert_response(scheme_files, 'id', 'files') + + +@pytest.mark.skip(reason='use submit arbitration evidence on demand, requires a dispute with prior submitted evidence') +def test_should_submit_arbitration_evidence(default_api): + response = default_api.disputes.submit_arbitration_evidence(PLACEHOLDER_DISPUTE_ID) + assert_response(response, 'http_metadata') + + +@pytest.mark.skip(reason='use get compiled submitted evidence on demand, requires a dispute with submitted evidence') +def test_should_get_compiled_submitted_evidence(default_api): + response = default_api.disputes.get_compiled_submitted_evidence(PLACEHOLDER_DISPUTE_ID) + assert_compiled_submitted_evidence_response(response) + + +@pytest.mark.skip(reason='use get compiled submitted arbitration evidence on demand, ' + 'requires a dispute with submitted arbitration evidence') +def test_should_get_compiled_submitted_arbitration_evidence(default_api): + response = default_api.disputes.get_compiled_submitted_arbitration_evidence(PLACEHOLDER_DISPUTE_ID) + assert_compiled_submitted_evidence_response(response) + + +def assert_compiled_submitted_evidence_response(response): + assert_response(response, + 'file_id', + '_links', + '_links.self') diff --git a/tests/events/events_client_test.py b/tests/events/events_client_test.py index 5e150260..5c331ebe 100644 --- a/tests/events/events_client_test.py +++ b/tests/events/events_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.events.events import RetrieveEventsRequest from checkout_sdk.events.events_client import EventsClient @@ -12,25 +13,38 @@ def client(mock_sdk_configuration, mock_api_client): class TestEventsClient: def test_retrieve_all_event_types(self, mocker, client: EventsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_all_event_types() == 'response' + assert_api_call(mock, 'event-types') def test_retrieve_events(self, mocker, client: EventsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.retrieve_events(RetrieveEventsRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = RetrieveEventsRequest() + + assert client.retrieve_events(query) == 'response' + assert_api_call(mock, 'events', query) def test_retrieve_event(self, mocker, client: EventsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_event('event_id') == 'response' + assert_api_call(mock, 'events/event_id') def test_retrieve_event_notification(self, mocker, client: EventsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_event_notification('event_id', 'notification_id') == 'response' + assert_api_call(mock, 'events/event_id/notifications/notification_id') def test_retry_webhook(self, mocker, client: EventsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.retry_webhook('event_id', 'webhook_id') == 'response' + assert_api_call(mock, 'events/event_id/webhooks/webhook_id/retry') def test_retry_all_webhooks(self, mocker, client: EventsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.retry_all_webhooks('event_id') == 'response' + assert_api_call(mock, 'events/event_id/webhooks/retry') diff --git a/tests/financial/financial_client_test.py b/tests/financial/financial_client_test.py index 441de45a..9f855602 100644 --- a/tests/financial/financial_client_test.py +++ b/tests/financial/financial_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.financial.financial import FinancialActionsQuery from checkout_sdk.financial.financial_client import FinancialClient @@ -12,5 +13,8 @@ def client(mock_sdk_configuration, mock_api_client): class TestFinancialClient: def test_should_query_financial_actions(self, mocker, client: FinancialClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.query(FinancialActionsQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = FinancialActionsQuery() + + assert client.query(query) == 'response' + assert_api_call(mock, 'financial-actions', query) diff --git a/tests/forex/forex_client_test.py b/tests/forex/forex_client_test.py index 92d0ea45..a51bf8de 100644 --- a/tests/forex/forex_client_test.py +++ b/tests/forex/forex_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.forex.forex import QuoteRequest, RatesQueryFilter from checkout_sdk.forex.forex_client import ForexClient @@ -12,9 +13,15 @@ def client(mock_sdk_configuration, mock_api_client): class TestForexClient: def test_should_request_quote(self, mocker, client: ForexClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_quote(QuoteRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = QuoteRequest() + + assert client.request_quote(body) == 'response' + assert_api_call(mock, 'forex/quotes', body) def test_should_get_rates(self, mocker, client: ForexClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.get_rates(RatesQueryFilter()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = RatesQueryFilter() + + assert client.get_rates(query) == 'response' + assert_api_call(mock, 'forex/rates', query) diff --git a/tests/forward/forward_client_test.py b/tests/forward/forward_client_test.py index b2673440..9f2fdb36 100644 --- a/tests/forward/forward_client_test.py +++ b/tests/forward/forward_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.forward.forward import ForwardRequest, SecretRequest from checkout_sdk.forward.forward_client import ForwardClient @@ -12,25 +13,40 @@ def client(mock_sdk_configuration, mock_api_client): class TestForwardClient: def test_should_forward_request(self, mocker, client: ForwardClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.forward_request(ForwardRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ForwardRequest() + + assert client.forward_request(body) == 'response' + assert_api_call(mock, 'forward', body) def test_should_get_forward_request(self, mocker, client: ForwardClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get('forward_id') == 'response' + assert_api_call(mock, 'forward/forward_id') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SecretRequest() + + assert client.create_secret(body) == 'response' + assert_api_call(mock, 'forward/secrets', body) def test_should_list_secrets(self, mocker, client: ForwardClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.list_secrets() == 'response' + assert_api_call(mock, 'forward/secrets') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = SecretRequest() + + assert client.update_secret('secret_name', body) == 'response' + assert_api_call(mock, 'forward/secrets/secret_name', body) def test_should_delete_secret(self, mocker, client: ForwardClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.delete_secret('secret_name') == 'response' + assert_api_call(mock, 'forward/secrets/secret_name') diff --git a/tests/identities/amlscreening/amlscreening_client_test.py b/tests/identities/amlscreening/amlscreening_client_test.py index c6f0a4a2..467573a0 100644 --- a/tests/identities/amlscreening/amlscreening_client_test.py +++ b/tests/identities/amlscreening/amlscreening_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.identities.amlscreening.amlscreening import AmlScreeningRequest from checkout_sdk.identities.amlscreening.amlscreening_client import AmlScreeningClient @@ -12,9 +13,14 @@ def client(mock_sdk_configuration, mock_api_client): 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = AmlScreeningRequest() + + assert client.create_aml_screening(body) == 'response' + assert_api_call(mock, 'aml-verifications', body) def test_should_get_aml_screening(self, mocker, client: AmlScreeningClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_aml_screening('scr_7hr7swleu6guzjqesyxmyodnya') == 'response' + assert_api_call(mock, 'aml-verifications/scr_7hr7swleu6guzjqesyxmyodnya') diff --git a/tests/identities/applicants/applicants_client_test.py b/tests/identities/applicants/applicants_client_test.py index 4ad6a2fa..f0bd0ecd 100644 --- a/tests/identities/applicants/applicants_client_test.py +++ b/tests/identities/applicants/applicants_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.identities.applicants.applicants import CreateApplicantRequest, UpdateApplicantRequest from checkout_sdk.identities.applicants.applicants_client import ApplicantsClient @@ -9,22 +10,30 @@ 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateApplicantRequest() + + assert client.create_applicant(body) == 'response' + assert_api_call(mock, 'applicants', body) def test_should_get_applicant(self, mocker, client: ApplicantsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_applicant('aplt_7hr7swleu6guzjqesyxmyodnya') == 'response' + assert_api_call(mock, 'applicants/aplt_7hr7swleu6guzjqesyxmyodnya') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdateApplicantRequest() + + assert client.update_applicant('aplt_7hr7swleu6guzjqesyxmyodnya', body) == 'response' + assert_api_call(mock, 'applicants/aplt_7hr7swleu6guzjqesyxmyodnya', body) def test_should_anonymize_applicant(self, mocker, client: ApplicantsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_applicant('aplt_7hr7swleu6guzjqesyxmyodnya') == 'response' + assert_api_call(mock, 'applicants/aplt_7hr7swleu6guzjqesyxmyodnya/anonymize') diff --git a/tests/identities/faceauthentication/faceauthentication_client_test.py b/tests/identities/faceauthentication/faceauthentication_client_test.py index 5e41eade..0809185b 100644 --- a/tests/identities/faceauthentication/faceauthentication_client_test.py +++ b/tests/identities/faceauthentication/faceauthentication_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.identities.faceauthentication.faceauthentication import ( FaceAuthenticationRequest, FaceAuthenticationAttemptRequest ) @@ -11,32 +12,46 @@ def client(mock_sdk_configuration, mock_api_client): return FaceAuthenticationClient(api_client=mock_api_client, configuration=mock_sdk_configuration) -# tests +_FAV_ID = 'fav_mtta050yudd54y5iqb5ijh8jtvz' +_ATTEMPT_ID = 'fatp_nk1wbmmczqumwt95k3v39mhbh2w' + 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = FaceAuthenticationRequest() + + assert client.create_face_authentication(body) == 'response' + assert_api_call(mock, 'face-authentications', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_face_authentication(_FAV_ID) == 'response' + assert_api_call(mock, f'face-authentications/{_FAV_ID}') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.anonymize_face_authentication(_FAV_ID) == 'response' + assert_api_call(mock, f'face-authentications/{_FAV_ID}/anonymize') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = FaceAuthenticationAttemptRequest() + + assert client.create_face_authentication_attempt(_FAV_ID, body) == 'response' + assert_api_call(mock, f'face-authentications/{_FAV_ID}/attempts', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_face_authentication_attempts(_FAV_ID) == 'response' + assert_api_call(mock, f'face-authentications/{_FAV_ID}/attempts') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_face_authentication_attempt(_FAV_ID, _ATTEMPT_ID) == 'response' + assert_api_call(mock, f'face-authentications/{_FAV_ID}/attempts/{_ATTEMPT_ID}') diff --git a/tests/identities/iddocumentverification/iddocumentverification_client_test.py b/tests/identities/iddocumentverification/iddocumentverification_client_test.py index 9aa60fe2..7d0f64d7 100644 --- a/tests/identities/iddocumentverification/iddocumentverification_client_test.py +++ b/tests/identities/iddocumentverification/iddocumentverification_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.identities.iddocumentverification.iddocumentverification import ( IdDocumentVerificationRequest, IdDocumentVerificationAttemptRequest ) @@ -11,35 +12,48 @@ 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = IdDocumentVerificationRequest() + + assert client.create_id_document_verification(body) == 'response' + assert_api_call(mock, 'id-document-verifications', body) def test_should_get_id_document_verification(self, mocker, client: IdDocumentVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification('iddoc_12345') == 'response' + assert_api_call(mock, 'id-document-verifications/iddoc_12345') def test_should_anonymize_id_document_verification(self, mocker, client: IdDocumentVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_id_document_verification('iddoc_12345') == 'response' + assert_api_call(mock, 'id-document-verifications/iddoc_12345/anonymize') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = IdDocumentVerificationAttemptRequest() + + assert client.create_id_document_verification_attempt('iddoc_12345', body) == 'response' + assert_api_call(mock, 'id-document-verifications/iddoc_12345/attempts', body) def test_should_get_id_document_verification_attempts(self, mocker, client: IdDocumentVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification_attempts('iddoc_12345') == 'response' + assert_api_call(mock, 'id-document-verifications/iddoc_12345/attempts') def test_should_get_id_document_verification_attempt(self, mocker, client: IdDocumentVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification_attempt('iddoc_12345', 'attempt_67890') == 'response' + assert_api_call(mock, 'id-document-verifications/iddoc_12345/attempts/attempt_67890') def test_should_get_id_document_verification_report(self, mocker, client: IdDocumentVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_id_document_verification_report('iddoc_12345') == 'response' + assert_api_call(mock, 'id-document-verifications/iddoc_12345/pdf-report') diff --git a/tests/identities/identityverification/identityverification_client_test.py b/tests/identities/identityverification/identityverification_client_test.py index 8fd8e815..4600b6e5 100644 --- a/tests/identities/identityverification/identityverification_client_test.py +++ b/tests/identities/identityverification/identityverification_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.identities.identityverification.identityverification import ( IdentityVerificationRequest, IdentityVerificationAndAttemptRequest, IdentityVerificationAttemptRequest ) @@ -11,39 +12,55 @@ 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = IdentityVerificationAndAttemptRequest() + + assert client.create_identity_verification_and_attempt(body) == 'response' + assert_api_call(mock, 'create-and-open-idv', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = IdentityVerificationRequest() + + assert client.create_identity_verification(body) == 'response' + assert_api_call(mock, 'identity-verifications', body) def test_should_get_identity_verification(self, mocker, client: IdentityVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification('idv_12345') == 'response' + assert_api_call(mock, 'identity-verifications/idv_12345') def test_should_anonymize_identity_verification(self, mocker, client: IdentityVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.anonymize_identity_verification('idv_12345') == 'response' + assert_api_call(mock, 'identity-verifications/idv_12345/anonymize') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = IdentityVerificationAttemptRequest() + + assert client.create_identity_verification_attempt('idv_12345', body) == 'response' + assert_api_call(mock, 'identity-verifications/idv_12345/attempts', body) def test_should_get_identity_verification_attempts(self, mocker, client: IdentityVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification_attempts('idv_12345') == 'response' + assert_api_call(mock, 'identity-verifications/idv_12345/attempts') def test_should_get_identity_verification_attempt(self, mocker, client: IdentityVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification_attempt('idv_12345', 'attempt_67890') == 'response' + assert_api_call(mock, 'identity-verifications/idv_12345/attempts/attempt_67890') def test_should_get_identity_verification_report(self, mocker, client: IdentityVerificationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_identity_verification_report('idv_12345') == 'response' + assert_api_call(mock, 'identity-verifications/idv_12345/pdf-report') diff --git a/tests/instruments/instruments_client_test.py b/tests/instruments/instruments_client_test.py index 1ed85b8a..a69ec92e 100644 --- a/tests/instruments/instruments_client_test.py +++ b/tests/instruments/instruments_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.common.enums import Country, Currency from checkout_sdk.instruments.instruments import CreateTokenInstrumentRequest, UpdateCardInstrumentRequest, \ BankAccountFieldQuery @@ -14,21 +15,35 @@ def client(mock_sdk_configuration, mock_api_client): class TestInstrumentsClient: def test_should_get_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get('instrument_id') == 'response' + assert_api_call(mock, 'instruments/instrument_id') def test_should_create_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create(CreateTokenInstrumentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateTokenInstrumentRequest() + + assert client.create(body) == 'response' + assert_api_call(mock, 'instruments', body) def test_should_update_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update('instrument_id', UpdateCardInstrumentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdateCardInstrumentRequest() + + assert client.update('instrument_id', body) == 'response' + assert_api_call(mock, 'instruments/instrument_id', body) def test_should_delete_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.delete('instrument_id') == 'response' + assert_api_call(mock, 'instruments/instrument_id') def test_should_get_bank_account_field_formatting(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.get_bank_account_field_formatting(Country.GB, Currency.GBP, BankAccountFieldQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = BankAccountFieldQuery() + + assert client.get_bank_account_field_formatting(Country.GB, Currency.GBP, query) == 'response' + # Country / Currency are enums; their .value is what build_path concatenates. + assert_api_call(mock, 'validation/bank-accounts/GB/GBP', query) diff --git a/tests/instruments/instruments_previous_client_test.py b/tests/instruments/instruments_previous_client_test.py index 711a5c0f..355bc78a 100644 --- a/tests/instruments/instruments_previous_client_test.py +++ b/tests/instruments/instruments_previous_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.instruments.instruments_previous import CreateInstrumentRequest, UpdateInstrumentRequest from checkout_sdk.instruments.instruments_client_previous import InstrumentsClient @@ -12,17 +13,27 @@ def client(mock_sdk_configuration, mock_api_client): class TestInstrumentsClient: def test_should_get_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get('instrument_id') == 'response' + assert_api_call(mock, 'instruments/instrument_id') def test_should_create_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create(CreateInstrumentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateInstrumentRequest() + + assert client.create(body) == 'response' + assert_api_call(mock, 'instruments', body) def test_should_update_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update('instrument_id', UpdateInstrumentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdateInstrumentRequest() + + assert client.update('instrument_id', body) == 'response' + assert_api_call(mock, 'instruments/instrument_id', body) def test_should_delete_instrument(self, mocker, client: InstrumentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.delete('instrument_id') == 'response' + assert_api_call(mock, 'instruments/instrument_id') diff --git a/tests/issuing/cardholders_issuing_integration_test.py b/tests/issuing/cardholders_issuing_integration_test.py index da632b2f..1ce18796 100644 --- a/tests/issuing/cardholders_issuing_integration_test.py +++ b/tests/issuing/cardholders_issuing_integration_test.py @@ -1,9 +1,11 @@ from __future__ import absolute_import -from checkout_sdk.issuing.cardholders import CardholderType -from tests.checkout_test_utils import assert_response +from checkout_sdk.issuing.cardholders import CardholderType, CardholderRequest +from tests.checkout_test_utils import assert_response, phone, address +# tests + def test_should_create_cardholder(issuing_checkout_api, cardholder): assert_response(cardholder, 'id', @@ -30,6 +32,15 @@ def test_should_get_cardholder(issuing_checkout_api, cardholder): assert 'X-123456-N11' == cardholder_response.reference +def test_should_update_cardholder(issuing_checkout_api, cardholder): + request = build_update_cardholder_request() + + response = issuing_checkout_api.issuing.update_cardholder(cardholder.id, request) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_get_cardholder_cards(issuing_checkout_api, cardholder): cardholder_response = issuing_checkout_api.issuing.get_cardholder_cards(cardholder.id) @@ -37,3 +48,18 @@ def test_should_get_cardholder_cards(issuing_checkout_api, cardholder): for card in cardholder_response.cards: assert cardholder.id == card.cardholder_id + + +# common methods + +def build_update_cardholder_request() -> CardholderRequest: + request = CardholderRequest() + request.first_name = 'John' + request.middle_name = 'Fitzgerald' + request.last_name = 'Kennedy' + request.email = 'john.kennedy@myemaildomain.com' + request.phone_number = phone() + request.date_of_birth = '1985-05-15' + request.billing_address = address() + request.residency_address = address() + return request diff --git a/tests/issuing/cards_issuing_integration_test.py b/tests/issuing/cards_issuing_integration_test.py index e0539627..c80df2f5 100644 --- a/tests/issuing/cards_issuing_integration_test.py +++ b/tests/issuing/cards_issuing_integration_test.py @@ -1,13 +1,18 @@ +from datetime import datetime, timedelta + import pytest from checkout_sdk.issuing.cards import PasswordEnrollmentRequest, SecurityPair, UpdateThreeDsEnrollmentRequest, \ - CardCredentialsQuery, RevokeRequest, RevokeReason, SuspendRequest, SuspendReason + CardCredentialsQuery, RevokeRequest, RevokeReason, SuspendRequest, SuspendReason, UpdateCardRequest, CardMetadata, \ + VirtualCardRenewRequest, ScheduleCardRevocationRequest from tests.checkout_test_utils import assert_response, phone @pytest.mark.skip("Avoid creating cards all the time") @pytest.mark.usefixtures("card") class TestCardsIssuing: + # tests + def test_should_create_card(self, card): assert_response(card, 'id', @@ -44,6 +49,54 @@ def test_should_get_card_details(self, issuing_checkout_api, cardholder, card): assert response.card_product_id == 'pro_3fn6pv2ikshurn36dbd3iysyha' assert response.reference == 'X-123456-N11' + def test_should_update_card(self, issuing_checkout_api, card): + request = build_update_card_request() + + response = issuing_checkout_api.issuing.update_card(card.id, request) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_renew_card(self, issuing_checkout_api, card): + request = VirtualCardRenewRequest() + request.reference = 'RENEW-REF-123' + + response = issuing_checkout_api.issuing.renew_card(card.id, request) + + assert_response(response, 'id') + assert response.http_metadata.status_code == 201 + + def test_should_schedule_card_revocation(self, issuing_checkout_api, active_card): + request = ScheduleCardRevocationRequest() + request.revocation_date = (datetime.utcnow() + timedelta(days=7)).strftime('%Y-%m-%d') + + response = issuing_checkout_api.issuing.schedule_card_revocation(active_card.id, request) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_delete_card_revocation(self, issuing_checkout_api, active_card): + request = ScheduleCardRevocationRequest() + request.revocation_date = (datetime.utcnow() + timedelta(days=7)).strftime('%Y-%m-%d') + issuing_checkout_api.issuing.schedule_card_revocation(active_card.id, request) + + response = issuing_checkout_api.issuing.delete_card_revocation(active_card.id) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_get_digital_card(self, issuing_checkout_api): + digital_card_id = 'dcr_5ngxzsynm2me3oxf73esbhda6q' + + response = issuing_checkout_api.issuing.get_digital_card(digital_card_id) + + assert_response(response, + 'id', + 'card_id', + 'last_four', + 'status') + assert response.id == digital_card_id + def test_should_enroll_into_three_ds(self, issuing_checkout_api, card): request = PasswordEnrollmentRequest() request.password = self.__get_pass() @@ -143,3 +196,21 @@ def test_should_suspend_card(self, issuing_checkout_api, active_card): def __get_pass(self): return 'Xtui43FvfiZ' + + +# common methods + +def build_update_card_request() -> UpdateCardRequest: + metadata = CardMetadata() + metadata.udf1 = 'UDF1' + metadata.udf2 = 'UDF2' + metadata.udf3 = 'UDF3' + metadata.udf4 = 'UDF4' + metadata.udf5 = 'UDF5' + + request = UpdateCardRequest() + request.reference = 'UPDATED-REF-123' + request.metadata = metadata + request.expiry_month = 12 + request.expiry_year = 2030 + return request diff --git a/tests/issuing/conftest.py b/tests/issuing/conftest.py index d349db92..d0b4c85a 100644 --- a/tests/issuing/conftest.py +++ b/tests/issuing/conftest.py @@ -5,7 +5,8 @@ from checkout_sdk.common.enums import DocumentType, Currency from checkout_sdk.issuing.cardholders import CardholderRequest, CardholderDocument, CardholderType from checkout_sdk.issuing.cards import CardLifetime, LifetimeUnit, VirtualCardRequest -from checkout_sdk.issuing.controls import VelocityControlRequest, VelocityLimit, VelocityWindow, VelocityWindowType +from checkout_sdk.issuing.controls import VelocityControlRequest, VelocityLimit, VelocityWindow, VelocityWindowType, \ + CreateControlGroupRequest, FailIfType, MccControlGroupControl, MccLimit, MccLimitType, ControlProfileRequest from checkout_sdk.issuing.testing import CardSimulation, Merchant, TransactionSimulation, TransactionType, \ AuthorizationType, CardAuthorizationRequest from checkout_sdk.oauth_scopes import OAuthScopes @@ -121,6 +122,27 @@ def transaction(issuing_checkout_api, active_card): return transaction +@pytest.fixture(scope='class') +def control_group(issuing_checkout_api, card): + request = get_control_group_request(card.id) + + control_group = issuing_checkout_api.issuing.create_control_group(request) + + assert_response(control_group, 'id') + return control_group + + +@pytest.fixture(scope='class') +def control_profile(issuing_checkout_api): + request = ControlProfileRequest() + request.name = 'Test Control Profile' + + control_profile = issuing_checkout_api.issuing.create_control_profile(request) + + assert_response(control_profile, 'id') + return control_profile + + def get_card_request(cardholder): lifetime = CardLifetime() lifetime.unit = LifetimeUnit.MONTHS @@ -135,3 +157,21 @@ def get_card_request(cardholder): request.is_single_use = False return request + + +def get_control_group_request(target_id: str): + mcc_limit = MccLimit() + mcc_limit.type = MccLimitType.BLOCK + mcc_limit.mcc_list = ['5411', '5422'] + + mcc_control = MccControlGroupControl() + mcc_control.description = 'Block grocery stores' + mcc_control.mcc_limit = mcc_limit + + request = CreateControlGroupRequest() + request.target_id = target_id + request.fail_if = FailIfType.ALL_FAIL + request.description = 'Integration test control group' + request.controls = [mcc_control] + + return request diff --git a/tests/issuing/control_groups_issuing_integration_test.py b/tests/issuing/control_groups_issuing_integration_test.py new file mode 100644 index 00000000..9322c5bc --- /dev/null +++ b/tests/issuing/control_groups_issuing_integration_test.py @@ -0,0 +1,55 @@ +import pytest + +from checkout_sdk.issuing.controls import ControlGroupQueryTarget, FailIfType +from tests.checkout_test_utils import assert_response +from tests.issuing.conftest import get_control_group_request + + +@pytest.mark.skip("Avoid creating cards all the time") +@pytest.mark.usefixtures("card", "control_group") +class TestControlGroupsIssuing: + # tests + + def test_should_create_control_group(self, card, control_group): + assert_response(control_group, + 'id', + 'target_id', + 'fail_if', + 'description', + 'controls') + + assert control_group.id.startswith('cgr_') + assert control_group.target_id == card.id + assert control_group.fail_if == FailIfType.ALL_FAIL + assert control_group.description == 'Integration test control group' + + def test_should_get_target_control_groups(self, issuing_checkout_api, card, control_group): + query = ControlGroupQueryTarget() + query.target_id = card.id + + response = issuing_checkout_api.issuing.get_target_control_groups(query) + + assert_response(response, 'control_groups') + assert any(cg.id == control_group.id for cg in response.control_groups) + + def test_should_get_control_group_details(self, issuing_checkout_api, control_group): + response = issuing_checkout_api.issuing.get_control_group_details(control_group.id) + + assert_response(response, + 'id', + 'target_id', + 'fail_if', + 'description') + + assert response.id == control_group.id + assert response.target_id == control_group.target_id + assert response.fail_if == control_group.fail_if + + def test_should_delete_control_group(self, issuing_checkout_api, card): + request = get_control_group_request(card.id) + created = issuing_checkout_api.issuing.create_control_group(request) + + response = issuing_checkout_api.issuing.delete_control_group(created.id) + + assert_response(response) + assert response.http_metadata.status_code == 200 diff --git a/tests/issuing/control_profiles_issuing_integration_test.py b/tests/issuing/control_profiles_issuing_integration_test.py new file mode 100644 index 00000000..0e4d7815 --- /dev/null +++ b/tests/issuing/control_profiles_issuing_integration_test.py @@ -0,0 +1,81 @@ +import pytest + +from checkout_sdk.issuing.controls import ControlGroupQueryTarget, ControlProfileRequest +from tests.checkout_test_utils import assert_response + + +@pytest.mark.skip("Avoid creating cards all the time") +@pytest.mark.usefixtures("control_profile") +class TestControlProfilesIssuing: + # tests + + def test_should_create_control_profile(self, control_profile): + assert_response(control_profile, + 'id', + 'name') + + assert control_profile.name == 'Test Control Profile' + + def test_should_get_all_control_profiles(self, issuing_checkout_api, control_profile): + query = ControlGroupQueryTarget() + query.target_id = control_profile.id + + response = issuing_checkout_api.issuing.get_all_control_profiles(query) + + assert_response(response, 'control_profiles') + assert any(cp.id == control_profile.id for cp in response.control_profiles) + + def test_should_get_control_profile_details(self, issuing_checkout_api, control_profile): + response = issuing_checkout_api.issuing.get_control_profile_details(control_profile.id) + + assert_response(response, + 'id', + 'name') + + assert response.id == control_profile.id + assert response.name == control_profile.name + + def test_should_update_control_profile(self, issuing_checkout_api, control_profile): + request = build_update_control_profile_request() + + response = issuing_checkout_api.issuing.update_control_profile(control_profile.id, request) + + assert_response(response, 'id', 'name') + assert response.id == control_profile.id + assert response.name == 'Updated Control Profile' + + def test_should_add_target_to_control_profile(self, issuing_checkout_api, active_card, control_profile): + response = issuing_checkout_api.issuing.add_target_to_control_profile(control_profile.id, active_card.id) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_remove_target_from_control_profile(self, issuing_checkout_api, active_card, control_profile): + issuing_checkout_api.issuing.add_target_to_control_profile(control_profile.id, active_card.id) + + response = issuing_checkout_api.issuing.remove_target_from_control_profile(control_profile.id, active_card.id) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_delete_control_profile(self, issuing_checkout_api): + created = issuing_checkout_api.issuing.create_control_profile(build_create_control_profile_request()) + + response = issuing_checkout_api.issuing.delete_control_profile(created.id) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + +# common methods + +def build_create_control_profile_request() -> ControlProfileRequest: + request = ControlProfileRequest() + request.name = 'Test Control Profile' + return request + + +def build_update_control_profile_request() -> ControlProfileRequest: + request = ControlProfileRequest() + request.name = 'Updated Control Profile' + return request diff --git a/tests/issuing/disputes_issuing_integration_test.py b/tests/issuing/disputes_issuing_integration_test.py new file mode 100644 index 00000000..1b741496 --- /dev/null +++ b/tests/issuing/disputes_issuing_integration_test.py @@ -0,0 +1,90 @@ +import uuid + +import pytest + +from checkout_sdk.issuing.disputes import CreateDisputeRequest, DisputeEvidence, EscalateDisputeRequest +from tests.checkout_test_utils import assert_response + + +@pytest.mark.skip("Requires permissions to create disputes and simulate transactions") +class TestDisputesIssuing: + # tests + + def test_should_create_dispute(self, issuing_checkout_api, transaction): + request = build_create_dispute_request(transaction.id) + idempotency_key = str(uuid.uuid4()) + + response = issuing_checkout_api.issuing.create_dispute(request, idempotency_key) + + assert_response(response, + 'id', + 'transaction_id', + 'reason', + 'status') + assert response.id.startswith('idsp_') + assert response.transaction_id == request.transaction_id + assert response.reason == request.reason + + def test_should_get_dispute_details(self, issuing_checkout_api, transaction): + request = build_create_dispute_request(transaction.id) + created = issuing_checkout_api.issuing.create_dispute(request, str(uuid.uuid4())) + + response = issuing_checkout_api.issuing.get_dispute_details(created.id) + + assert_response(response, + 'id', + 'transaction_id', + 'reason') + assert response.id == created.id + assert response.transaction_id == created.transaction_id + + def test_should_cancel_dispute(self, issuing_checkout_api, transaction): + request = build_create_dispute_request(transaction.id) + idempotency_key = str(uuid.uuid4()) + created = issuing_checkout_api.issuing.create_dispute(request, idempotency_key) + + response = issuing_checkout_api.issuing.cancel_dispute(created.id, idempotency_key) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_escalate_dispute(self, issuing_checkout_api, transaction): + request = build_create_dispute_request(transaction.id) + idempotency_key = str(uuid.uuid4()) + created = issuing_checkout_api.issuing.create_dispute(request, idempotency_key) + + response = issuing_checkout_api.issuing.escalate_dispute( + created.id, build_escalate_dispute_request(), idempotency_key) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + +# common methods + +def build_create_dispute_request(transaction_id: str) -> CreateDisputeRequest: + evidence = DisputeEvidence() + evidence.name = 'receipt.pdf' + evidence.content = 'SGVsbG8gV29ybGQ=' + evidence.description = 'Transaction receipt showing unauthorized charge' + + request = CreateDisputeRequest() + request.transaction_id = transaction_id + request.reason = '4837' + request.evidence = [evidence] + request.amount = 1000 + request.justification = 'Customer reports unauthorized transaction' + return request + + +def build_escalate_dispute_request() -> EscalateDisputeRequest: + evidence = DisputeEvidence() + evidence.name = 'location_evidence.pdf' + evidence.content = 'TG9jYXRpb24gRXZpZGVuY2U=' + evidence.description = 'GPS data showing customer location during transaction' + + request = EscalateDisputeRequest() + request.justification = 'Merchant response was insufficient. Escalating to pre-arbitration.' + request.additional_evidence = [evidence] + request.amount = 800 + return request diff --git a/tests/issuing/issuing_client_test.py b/tests/issuing/issuing_client_test.py index 59303bc3..34493582 100644 --- a/tests/issuing/issuing_client_test.py +++ b/tests/issuing/issuing_client_test.py @@ -1,11 +1,17 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.issuing.cardholders import CardholderRequest from checkout_sdk.issuing.cards import PhysicalCardRequest, PasswordEnrollmentRequest, UpdateThreeDsEnrollmentRequest, \ - CardCredentialsQuery, RevokeRequest, SuspendRequest -from checkout_sdk.issuing.controls import MccControlRequest, CardControlsQuery, UpdateCardControlRequest + CardCredentialsQuery, RevokeRequest, SuspendRequest, UpdateCardRequest, VirtualCardRenewRequest, \ + ScheduleCardRevocationRequest +from checkout_sdk.issuing.controls import MccControlRequest, CardControlsQuery, UpdateCardControlRequest, \ + CreateControlGroupRequest, ControlGroupQueryTarget, ControlProfileRequest +from checkout_sdk.issuing.disputes import CreateDisputeRequest, EscalateDisputeRequest from checkout_sdk.issuing.issuing_client import IssuingClient -from checkout_sdk.issuing.testing import CardAuthorizationRequest, SimulationRequest +from checkout_sdk.issuing.testing import CardAuthorizationRequest, SimulationRequest, \ + CardRefundAuthorizationRequest, SimulateOobAuthenticationRequest +from checkout_sdk.issuing.transactions import TransactionsQueryFilter @pytest.fixture(scope='class') @@ -16,85 +22,309 @@ def client(mock_sdk_configuration, mock_api_client): class TestIssuingClient: def test_should_create_cardholder(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_cardholder(CardholderRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CardholderRequest() + + assert client.create_cardholder(body) == 'response' + assert_api_call(mock, 'issuing/cardholders', body) def test_should_get_cardholder(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_cardholder('cardholder_id') == 'response' + assert_api_call(mock, 'issuing/cardholders/cardholder_id') + + def test_should_update_cardholder(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = CardholderRequest() + + assert client.update_cardholder('cardholder_id', body) == 'response' + assert_api_call(mock, 'issuing/cardholders/cardholder_id', body) def test_should_get_cardholder_cards(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_cardholder_cards('cardholder_id') == 'response' + assert_api_call(mock, 'issuing/cardholders/cardholder_id/cards') def test_should_create_card(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_card(PhysicalCardRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PhysicalCardRequest() + + assert client.create_card(body) == 'response' + assert_api_call(mock, 'issuing/cards', body) def test_should_get_card_details(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_card_details('card_id') == 'response' + assert_api_call(mock, 'issuing/cards/card_id') + + def test_should_update_card(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdateCardRequest() + + assert client.update_card('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id', body) def test_should_enroll_three_ds(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.enroll_three_ds('card_id', PasswordEnrollmentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PasswordEnrollmentRequest() + + assert client.enroll_three_ds('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id/3ds-enrollment', body) def test_should_update_three_ds_enrollment(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update_three_ds_enrollment('card_id', UpdateThreeDsEnrollmentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdateThreeDsEnrollmentRequest() + + assert client.update_three_ds_enrollment('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id/3ds-enrollment', body) def test_should_get_three_ds_details(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_three_ds_details('card_id') == 'response' + assert_api_call(mock, 'issuing/cards/card_id/3ds-enrollment') def test_should_activate_card(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.activate_card('card_id') == 'response' + assert_api_call(mock, 'issuing/cards/card_id/activate') def test_should_get_card_credentials(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.get_card_credentials('card_id', CardCredentialsQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = CardCredentialsQuery() + + assert client.get_card_credentials('card_id', query) == 'response' + # query is passed as the `params` positional arg of ApiClient.get + # (path, authorization, params) — same position the helper checks as body. + assert_api_call(mock, 'issuing/cards/card_id/credentials', query) + + def test_should_renew_card(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = VirtualCardRenewRequest() + + assert client.renew_card('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id/renew', body) def test_should_revoke_card(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.revoke_card('card_id', RevokeRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = RevokeRequest() + + assert client.revoke_card('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id/revoke', body) + + def test_should_schedule_card_revocation(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ScheduleCardRevocationRequest() + + assert client.schedule_card_revocation('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id/schedule-revocation', body) + + def test_should_delete_card_revocation(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + + assert client.delete_card_revocation('card_id') == 'response' + assert_api_call(mock, 'issuing/cards/card_id/schedule-revocation') def test_should_suspend_card(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.suspend_card('card_id', SuspendRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SuspendRequest() + + assert client.suspend_card('card_id', body) == 'response' + assert_api_call(mock, 'issuing/cards/card_id/suspend', body) + + def test_should_get_digital_card(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_digital_card('digital_card_id') == 'response' + assert_api_call(mock, 'issuing/digital-cards/digital_card_id') + + def test_should_get_list_transactions(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = TransactionsQueryFilter() + + assert client.get_list_transactions(query) == 'response' + assert_api_call(mock, 'issuing/transactions', query) + + def test_should_get_single_transaction(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_single_transaction('transaction_id') == 'response' + assert_api_call(mock, 'issuing/transactions/transaction_id') def test_should_create_control(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_control(MccControlRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = MccControlRequest() + + assert client.create_control(body) == 'response' + assert_api_call(mock, 'issuing/controls', body) def test_should_get_card_controls(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.get_card_controls(CardControlsQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = CardControlsQuery() + + assert client.get_card_controls(query) == 'response' + assert_api_call(mock, 'issuing/controls', query) def test_should_get_card_control_details(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_card_control_details('control_id') == 'response' + assert_api_call(mock, 'issuing/controls/control_id') def test_should_update_card_control(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_card_control('control_id', UpdateCardControlRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = UpdateCardControlRequest() + + assert client.update_card_control('control_id', body) == 'response' + assert_api_call(mock, 'issuing/controls/control_id', body) def test_should_remove_control(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.remove_control('control_id') == 'response' + assert_api_call(mock, 'issuing/controls/control_id') + + def test_should_create_control_group(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateControlGroupRequest() + + assert client.create_control_group(body) == 'response' + assert_api_call(mock, 'issuing/controls/control-groups', body) + + def test_should_get_target_control_groups(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = ControlGroupQueryTarget() + + assert client.get_target_control_groups(query) == 'response' + assert_api_call(mock, 'issuing/controls/control-groups', query) + + def test_should_get_control_group_details(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_control_group_details('control_group_id') == 'response' + assert_api_call(mock, 'issuing/controls/control-groups/control_group_id') + + def test_should_delete_control_group(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + + assert client.delete_control_group('control_group_id') == 'response' + assert_api_call(mock, 'issuing/controls/control-groups/control_group_id') + + def test_should_create_control_profile(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ControlProfileRequest() + + assert client.create_control_profile(body) == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles', body) + + def test_should_get_all_control_profiles(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = ControlGroupQueryTarget() + + assert client.get_all_control_profiles(query) == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles', query) + + def test_should_get_control_profile_details(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_control_profile_details('control_profile_id') == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles/control_profile_id') + + def test_should_update_control_profile(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = ControlProfileRequest() + + assert client.update_control_profile('control_profile_id', body) == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles/control_profile_id', body) + + def test_should_delete_control_profile(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + + assert client.delete_control_profile('control_profile_id') == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles/control_profile_id') + + def test_should_add_target_to_control_profile(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.add_target_to_control_profile('control_profile_id', 'target_id') == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles/control_profile_id/add/target_id') + + def test_should_remove_target_from_control_profile(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.remove_target_from_control_profile('control_profile_id', 'target_id') == 'response' + assert_api_call(mock, 'issuing/controls/control-profiles/control_profile_id/remove/target_id') + + def test_should_create_dispute(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateDisputeRequest() + + assert client.create_dispute(body) == 'response' + assert_api_call(mock, 'issuing/disputes', body) + + def test_should_get_dispute_details(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_dispute_details('dispute_id') == 'response' + assert_api_call(mock, 'issuing/disputes/dispute_id') + + def test_should_cancel_dispute(self, mocker, client: IssuingClient): + # cancel_dispute passes body=None explicitly to ApiClient.post for the + # idempotency-key 4th-arg signature, so we don't assert a body. + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.cancel_dispute('dispute_id') == 'response' + assert_api_call(mock, 'issuing/disputes/dispute_id/cancel') + + def test_should_escalate_dispute(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = EscalateDisputeRequest() + + assert client.escalate_dispute('dispute_id', body) == 'response' + assert_api_call(mock, 'issuing/disputes/dispute_id/escalate', body) def test_should_simulate_authorization(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.simulate_authorization(CardAuthorizationRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CardAuthorizationRequest() + + assert client.simulate_authorization(body) == 'response' + assert_api_call(mock, 'issuing/simulate/authorizations', body) def test_should_simulate_increment(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.simulate_increment('transaction_id', SimulationRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SimulationRequest() + + assert client.simulate_increment('transaction_id', body) == 'response' + assert_api_call(mock, 'issuing/simulate/authorizations/transaction_id/authorizations', body) def test_should_simulate_clearing(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.simulate_clearing('transaction_id', SimulationRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SimulationRequest() + + assert client.simulate_clearing('transaction_id', body) == 'response' + assert_api_call(mock, 'issuing/simulate/authorizations/transaction_id/presentments', body) def test_should_simulate_reversal(self, mocker, client: IssuingClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.simulate_reversal('transaction_id', SimulationRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SimulationRequest() + + assert client.simulate_reversal('transaction_id', body) == 'response' + assert_api_call(mock, 'issuing/simulate/authorizations/transaction_id/reversals', body) + + def test_should_simulate_refund(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CardRefundAuthorizationRequest() + + assert client.simulate_refund('transaction_id', body) == 'response' + assert_api_call(mock, 'issuing/simulate/authorizations/transaction_id/refunds', body) + + def test_should_simulate_oob_authentication(self, mocker, client: IssuingClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SimulateOobAuthenticationRequest() + + assert client.simulate_oob_authentication(body) == 'response' + assert_api_call(mock, 'issuing/simulate/oob/authentication', body) diff --git a/tests/issuing/testing_issuing_integration_test.py b/tests/issuing/testing_issuing_integration_test.py index 692117a7..ca8e1988 100644 --- a/tests/issuing/testing_issuing_integration_test.py +++ b/tests/issuing/testing_issuing_integration_test.py @@ -2,12 +2,15 @@ from checkout_sdk.common.enums import Currency from checkout_sdk.issuing.testing import CardSimulation, TransactionSimulation, TransactionType, AuthorizationType, \ - CardAuthorizationRequest, Merchant, SimulationRequest + CardAuthorizationRequest, Merchant, SimulationRequest, CardRefundAuthorizationRequest, \ + SimulateOobAuthenticationRequest, OobSimulateTransactionDetails from tests.checkout_test_utils import assert_response @pytest.mark.skip("Avoid creating cards all the time") class TestTestingIssuing: + # tests + def test_should_simulate_authorization(self, issuing_checkout_api, active_card): card_simulation = CardSimulation() card_simulation.id = active_card.id @@ -65,3 +68,34 @@ def test_should_simulate_reversal(self, issuing_checkout_api, transaction): 'status') assert response.status == 'Reversed' + + def test_should_simulate_refund(self, issuing_checkout_api, transaction): + request = CardRefundAuthorizationRequest() + request.amount = 100 + + response = issuing_checkout_api.issuing.simulate_refund(transaction.id, request) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + def test_should_simulate_oob_authentication(self, issuing_checkout_api, active_card): + request = build_oob_authentication_request(active_card.id) + + response = issuing_checkout_api.issuing.simulate_oob_authentication(request) + + assert_response(response) + assert response.http_metadata.status_code == 200 + + +# common methods + +def build_oob_authentication_request(card_id: str) -> SimulateOobAuthenticationRequest: + details = OobSimulateTransactionDetails() + details.merchant_name = 'Acme Ltd' + details.purchase_amount = 100 + details.purchase_currency = Currency.GBP + + request = SimulateOobAuthenticationRequest() + request.card_id = card_id + request.transaction_details = details + return request diff --git a/tests/issuing/transactions_issuing_integration_test.py b/tests/issuing/transactions_issuing_integration_test.py new file mode 100644 index 00000000..eeafda50 --- /dev/null +++ b/tests/issuing/transactions_issuing_integration_test.py @@ -0,0 +1,40 @@ +import pytest + +from checkout_sdk.issuing.transactions import TransactionsQueryFilter +from tests.checkout_test_utils import assert_response + + +@pytest.mark.skip("Avoid creating cards all the time") +class TestTransactionsIssuing: + # tests + + def test_should_get_list_transactions(self, issuing_checkout_api): + query = TransactionsQueryFilter() + + response = issuing_checkout_api.issuing.get_list_transactions(query) + + assert_response(response, + 'limit', + 'skip', + 'total_count', + 'data') + assert len(response.data) > 0 + + def test_should_get_single_transaction(self, issuing_checkout_api): + query = TransactionsQueryFilter() + list_response = issuing_checkout_api.issuing.get_list_transactions(query) + + response = issuing_checkout_api.issuing.get_single_transaction(list_response.data[0].id) + + assert_response(response, + 'id', + 'created_on', + 'status', + 'transaction_type', + 'client', + 'entity', + 'card', + 'cardholder', + 'amounts', + 'merchant', + 'messages') diff --git a/tests/metadata/card_metadata_client_test.py b/tests/metadata/card_metadata_client_test.py index 88be1094..65d95e8b 100644 --- a/tests/metadata/card_metadata_client_test.py +++ b/tests/metadata/card_metadata_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.metadata.metadata import CardMetadataRequest from checkout_sdk.metadata.metadata_client import CardMetadataClient @@ -12,5 +13,8 @@ def client(mock_sdk_configuration, mock_api_client): class TestCardMetadataClient: def test_should_request_card_metadata(self, mocker, client: CardMetadataClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_card_metadata(CardMetadataRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CardMetadataRequest() + + assert client.request_card_metadata(body) == 'response' + assert_api_call(mock, 'metadata/card', body) diff --git a/tests/networktokens/__init__.py b/tests/networktokens/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/networktokens/network_tokens_client_test.py b/tests/networktokens/network_tokens_client_test.py new file mode 100644 index 00000000..3659325d --- /dev/null +++ b/tests/networktokens/network_tokens_client_test.py @@ -0,0 +1,41 @@ +import pytest + +from tests._assertions import assert_api_call +from checkout_sdk.networktokens.network_tokens import ProvisionNetworkTokenRequest, RequestCryptogramRequest, \ + DeleteNetworkTokenRequest +from checkout_sdk.networktokens.network_tokens_client import NetworkTokensClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return NetworkTokensClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +class TestNetworkTokensClient: + + def test_provision_network_token(self, mocker, client: NetworkTokensClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ProvisionNetworkTokenRequest() + + assert client.provision_network_token(body) == 'response' + assert_api_call(mock, 'network-tokens', body) + + def test_get_network_token(self, mocker, client: NetworkTokensClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_network_token('network_token_id') == 'response' + assert_api_call(mock, 'network-tokens/network_token_id') + + def test_request_cryptogram(self, mocker, client: NetworkTokensClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = RequestCryptogramRequest() + + assert client.request_cryptogram('network_token_id', body) == 'response' + assert_api_call(mock, 'network-tokens/network_token_id/cryptograms', body) + + def test_delete_network_token(self, mocker, client: NetworkTokensClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = DeleteNetworkTokenRequest() + + assert client.delete_network_token('network_token_id', body) == 'response' + assert_api_call(mock, 'network-tokens/network_token_id/delete', body) diff --git a/tests/networktokens/network_tokens_integration_test.py b/tests/networktokens/network_tokens_integration_test.py new file mode 100644 index 00000000..946db1ca --- /dev/null +++ b/tests/networktokens/network_tokens_integration_test.py @@ -0,0 +1,73 @@ +from __future__ import absolute_import + +import pytest + +from checkout_sdk.networktokens.network_tokens import ProvisionNetworkTokenRequest, RequestCryptogramRequest, \ + DeleteNetworkTokenRequest, NetworkTokenRequestIdSource, NetworkTokenTransactionType, NetworkTokenInitiatedBy, \ + NetworkTokenDeleteReason +from tests.checkout_test_utils import assert_response + +PLACEHOLDER_NETWORK_TOKEN_ID = 'nt_xgu3isllqfyu7ktpk5z2yxbwna' +PLACEHOLDER_INSTRUMENT_ID = 'src_wmlfc3zyhqzehihu7giusaaawu' + + +# tests +@pytest.mark.skip(reason='use network token endpoints on demand, requires preexisting instrument and network token ids') +def test_should_provision_network_token(oauth_api): + request = build_provision_network_token_request() + + response = oauth_api.network_tokens.provision_network_token(request) + assert_network_token_response(response) + + +@pytest.mark.skip(reason='use network token endpoints on demand, requires preexisting instrument and network token ids') +def test_should_get_network_token(oauth_api): + response = oauth_api.network_tokens.get_network_token(PLACEHOLDER_NETWORK_TOKEN_ID) + assert_network_token_response(response) + + +@pytest.mark.skip(reason='use network token endpoints on demand, requires preexisting instrument and network token ids') +def test_should_request_cryptogram(oauth_api): + request = build_request_cryptogram_request() + + response = oauth_api.network_tokens.request_cryptogram(PLACEHOLDER_NETWORK_TOKEN_ID, request) + assert_response(response, 'http_metadata', 'cryptogram') + + +@pytest.mark.skip(reason='use network token endpoints on demand, requires preexisting instrument and network token ids') +def test_should_delete_network_token(oauth_api): + request = build_delete_network_token_request() + + response = oauth_api.network_tokens.delete_network_token(PLACEHOLDER_NETWORK_TOKEN_ID, request) + assert_response(response, 'http_metadata') + + +# common methods + +def build_provision_network_token_request() -> ProvisionNetworkTokenRequest: + id_source = NetworkTokenRequestIdSource() + id_source.id = PLACEHOLDER_INSTRUMENT_ID + + request = ProvisionNetworkTokenRequest() + request.source = id_source + return request + + +def build_request_cryptogram_request() -> RequestCryptogramRequest: + request = RequestCryptogramRequest() + request.transaction_type = NetworkTokenTransactionType.ECOM + return request + + +def build_delete_network_token_request() -> DeleteNetworkTokenRequest: + request = DeleteNetworkTokenRequest() + request.initiated_by = NetworkTokenInitiatedBy.TOKEN_REQUESTOR + request.reason = NetworkTokenDeleteReason.OTHER + return request + + +def assert_network_token_response(response): + assert_response(response, + 'http_metadata', + 'card', + 'network_token') diff --git a/tests/paymentmethods/__init__.py b/tests/paymentmethods/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/paymentmethods/payment_methods_client_test.py b/tests/paymentmethods/payment_methods_client_test.py new file mode 100644 index 00000000..be379ae7 --- /dev/null +++ b/tests/paymentmethods/payment_methods_client_test.py @@ -0,0 +1,20 @@ +import pytest + +from tests._assertions import assert_api_call +from checkout_sdk.paymentmethods.payment_methods_client import PaymentMethodsClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return PaymentMethodsClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +class TestPaymentMethodsClient: + + def test_get_available_payment_methods(self, mocker, client: PaymentMethodsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_available_payment_methods('pc_test_123456') == 'response' + assert_api_call(mock, 'payment-methods') + # The client wraps the str into a query filter internally; verify the wrapping. + assert mock.call_args.args[2].processing_channel_id == 'pc_test_123456' diff --git a/tests/paymentmethods/payment_methods_integration_test.py b/tests/paymentmethods/payment_methods_integration_test.py new file mode 100644 index 00000000..f96425d8 --- /dev/null +++ b/tests/paymentmethods/payment_methods_integration_test.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import + +import os + +import pytest + +from checkout_sdk.exception import CheckoutApiException +from tests.checkout_test_utils import assert_response + +INVALID_PROCESSING_CHANNEL_ID = 'pc_test_invalid_channel_id' + + +# tests + +def test_should_get_available_payment_methods(oauth_api): + response = oauth_api.payment_methods.get_available_payment_methods( + os.environ.get('CHECKOUT_PROCESSING_CHANNEL_ID')) + assert_get_available_payment_methods_response(response) + + +def test_should_get_available_payment_methods_with_specific_processing_channel(oauth_api): + response = oauth_api.payment_methods.get_available_payment_methods( + os.environ.get('CHECKOUT_PROCESSING_CHANNEL_ID')) + assert_get_available_payment_methods_response(response) + assert_methods_have_valid_structure(response) + + +def test_should_throw_with_invalid_processing_channel_id(oauth_api): + with pytest.raises(CheckoutApiException) as exc_info: + oauth_api.payment_methods.get_available_payment_methods(INVALID_PROCESSING_CHANNEL_ID) + assert 'processing_channel_id_invalid' in exc_info.value.error_details + + +# common methods + +def assert_get_available_payment_methods_response(response): + assert_response(response, + 'http_metadata', + 'methods') + assert len(response.methods) > 0 + + +def assert_methods_have_valid_structure(response): + for method in response.methods: + assert hasattr(method, 'type') + assert method.type is not None diff --git a/tests/payments/applepay/applepay_client_test.py b/tests/payments/applepay/applepay_client_test.py index 1e3cbf8e..e7a02d3e 100644 --- a/tests/payments/applepay/applepay_client_test.py +++ b/tests/payments/applepay/applepay_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.applepay.applepay import UploadCertificateRequest, EnrollDomainRequest, \ GenerateSigningRequestRequest from checkout_sdk.payments.applepay.applepay_client import ApplePayClient @@ -10,18 +11,25 @@ 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = UploadCertificateRequest() + + assert client.upload_payment_processing_certificate(body) == 'response' + assert_api_call(mock, 'applepay/certificates', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = EnrollDomainRequest() + + assert client.enroll_domain(body) == 'response' + assert_api_call(mock, 'applepay/enrollments', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = GenerateSigningRequestRequest() + + assert client.generate_certificate_signing_request(body) == 'response' + assert_api_call(mock, 'applepay/signing-requests', body) diff --git a/tests/payments/cancel_scheduled_retry_integration_test.py b/tests/payments/cancel_scheduled_retry_integration_test.py new file mode 100644 index 00000000..bb510f6c --- /dev/null +++ b/tests/payments/cancel_scheduled_retry_integration_test.py @@ -0,0 +1,50 @@ +from __future__ import absolute_import + +import pytest + +from checkout_sdk.payments.payments import CancelScheduledRetryRequest +from tests.checkout_test_utils import assert_response, new_idempotency_key +from tests.payments.payments_test_utils import make_card_payment + + +# tests +@pytest.mark.skip(reason='use cancel scheduled retry on demand, only works on payments with a pending scheduled retry') +def test_should_cancel_scheduled_retry(default_api, oauth_api): + payment_response = make_card_payment(default_api) + + cancel_request = build_cancel_scheduled_retry_request(payment_response.reference) + + cancel_response = oauth_api.payments.cancel_scheduled_retry(payment_response.id, cancel_request) + assert_cancel_scheduled_retry_response(cancel_response) + + +@pytest.mark.skip(reason='use cancel scheduled retry on demand, only works on payments with a pending scheduled retry') +def test_should_cancel_scheduled_retry_idempotently(default_api, oauth_api): + payment_response = make_card_payment(default_api) + + cancel_request = build_cancel_scheduled_retry_request(payment_response.reference) + idempotency_key = new_idempotency_key() + + cancel_response_1 = oauth_api.payments.cancel_scheduled_retry(payment_response.id, cancel_request, idempotency_key) + assert_response(cancel_response_1) + + cancel_response_2 = oauth_api.payments.cancel_scheduled_retry(payment_response.id, cancel_request, idempotency_key) + assert_response(cancel_response_2) + + assert cancel_response_1.action_id == cancel_response_2.action_id + + +# common methods + +def build_cancel_scheduled_retry_request(reference: str) -> CancelScheduledRetryRequest: + request = CancelScheduledRetryRequest() + request.reference = reference + return request + + +def assert_cancel_scheduled_retry_response(response): + assert_response(response, + 'http_metadata', + 'reference', + 'action_id', + '_links') diff --git a/tests/payments/contexts/payment_contexts_client_test.py b/tests/payments/contexts/payment_contexts_client_test.py index 606a0a3c..43a70a4a 100644 --- a/tests/payments/contexts/payment_contexts_client_test.py +++ b/tests/payments/contexts/payment_contexts_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.contexts.contexts import PaymentContextsRequest from checkout_sdk.payments.contexts.contexts_client import PaymentContextsClient @@ -12,9 +13,14 @@ def client(mock_sdk_configuration, mock_api_client): class TestPaymentContextsClient: def test_should_create_payment_contexts(self, mocker, client: PaymentContextsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_payment_contexts(PaymentContextsRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentContextsRequest() + + assert client.create_payment_contexts(body) == 'response' + assert_api_call(mock, 'payment-contexts', body) def test_should_get_payment_context_details(self, mocker, client: PaymentContextsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_context_details('payment_contexts_id') == 'response' + assert_api_call(mock, 'payment-contexts/payment_contexts_id') diff --git a/tests/payments/googlepay/googlepay_client_test.py b/tests/payments/googlepay/googlepay_client_test.py index 08dc8b49..01855b13 100644 --- a/tests/payments/googlepay/googlepay_client_test.py +++ b/tests/payments/googlepay/googlepay_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.googlepay.googlepay import GooglePayEnrollmentRequest, GooglePayRegisterDomainRequest from checkout_sdk.payments.googlepay.googlepay_client import GooglePayClient @@ -9,22 +10,33 @@ def client(mock_sdk_configuration, mock_api_client): return GooglePayClient(api_client=mock_api_client, configuration=mock_sdk_configuration) -# tests +_ENTITY_ID = 'ent_uzm3uxtssvmuxnyrfdffcyjxeu' + 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = GooglePayEnrollmentRequest() + + assert client.create_enrollment(body) == 'response' + assert_api_call(mock, 'googlepay/enrollments', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = GooglePayRegisterDomainRequest() + + assert client.register_domain(_ENTITY_ID, body) == 'response' + assert_api_call(mock, f'googlepay/enrollments/{_ENTITY_ID}/domain', body) 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_registered_domains(_ENTITY_ID) == 'response' + assert_api_call(mock, f'googlepay/enrollments/{_ENTITY_ID}/domains') 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_enrollment_state(_ENTITY_ID) == 'response' + assert_api_call(mock, f'googlepay/enrollments/{_ENTITY_ID}/state') diff --git a/tests/payments/hosted/hosted_payments_client_test.py b/tests/payments/hosted/hosted_payments_client_test.py index c6f9c50d..d4fc265c 100644 --- a/tests/payments/hosted/hosted_payments_client_test.py +++ b/tests/payments/hosted/hosted_payments_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.hosted.hosted_payments import HostedPaymentsSessionRequest from checkout_sdk.payments.hosted.hosted_payments_client import HostedPaymentsClient @@ -11,10 +12,15 @@ def client(mock_sdk_configuration, mock_api_client): class TestHostedPaymentsClient: - def test_should_get_instrument(self, mocker, client: HostedPaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + def test_should_get_hosted_payments_page_details(self, mocker, client: HostedPaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_hosted_payments_page_details('hosted_payment_id') == 'response' + assert_api_call(mock, 'hosted-payments/hosted_payment_id') + + def test_should_create_hosted_payments_page_session(self, mocker, client: HostedPaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = HostedPaymentsSessionRequest() - def test_should_create_instrument(self, mocker, client: HostedPaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_hosted_payments_page_session(HostedPaymentsSessionRequest()) == 'response' + assert client.create_hosted_payments_page_session(body) == 'response' + assert_api_call(mock, 'hosted-payments', body) diff --git a/tests/payments/links/payments_links_client_test.py b/tests/payments/links/payments_links_client_test.py index 038f092a..62d79bed 100644 --- a/tests/payments/links/payments_links_client_test.py +++ b/tests/payments/links/payments_links_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.links.payments_client import PaymentsLinksClient from checkout_sdk.payments.links.payments_links import PaymentLinkRequest @@ -9,12 +10,17 @@ def client(mock_sdk_configuration, mock_api_client): return PaymentsLinksClient(api_client=mock_api_client, configuration=mock_sdk_configuration) -class TestHostedPaymentsClient: +class TestPaymentsLinksClient: + + def test_should_get_payment_link(self, mocker, client: PaymentsLinksClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - def test_should_get_instrument(self, mocker, client: PaymentsLinksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') assert client.get_payment_link('payment_link_id') == 'response' + assert_api_call(mock, 'payment-links/payment_link_id') + + def test_should_create_payment_link(self, mocker, client: PaymentsLinksClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentLinkRequest() - def test_should_create_instrument(self, mocker, client: PaymentsLinksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_payment_link(PaymentLinkRequest()) == 'response' + assert client.create_payment_link(body) == 'response' + assert_api_call(mock, 'payment-links', body) diff --git a/tests/payments/payments_client_test.py b/tests/payments/payments_client_test.py index 881ffaf4..331e5afc 100644 --- a/tests/payments/payments_client_test.py +++ b/tests/payments/payments_client_test.py @@ -1,9 +1,11 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.common.common import AccountHolder from checkout_sdk.payments.payments_client import PaymentsClient from checkout_sdk.payments.payments import PaymentRequest, PayoutRequest, CaptureRequest, AuthorizationRequest, \ - RequestProviderTokenSource, RefundRequest, VoidRequest + RequestProviderTokenSource, RefundRequest, VoidRequest, CancelScheduledRetryRequest, ReversePaymentRequest, \ + PaymentsSearchRequest @pytest.fixture(scope='class') @@ -14,61 +16,139 @@ def client(mock_sdk_configuration, mock_api_client): class TestPaymentsClient: def test_request_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payment(PaymentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentRequest() + + assert client.request_payment(body) == 'response' + assert_api_call(mock, 'payments', body) def test_request_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payment(PaymentRequest(), 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentRequest() + + assert client.request_payment(body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments', body) def test_request_payout(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payout(PayoutRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PayoutRequest() + + assert client.request_payout(body) == 'response' + assert_api_call(mock, 'payments', body) def test_request_payout_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payout(PayoutRequest(), 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PayoutRequest() + + assert client.request_payout(body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments', body) def test_get_payment_details(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_details('payment_id') == 'response' + assert_api_call(mock, 'payments/payment_id') def test_get_payment_actions(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_actions('payment_id') == 'response' + assert_api_call(mock, 'payments/payment_id/actions') + + def test_cancel_scheduled_retry(self, mocker, client: PaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CancelScheduledRetryRequest() + + assert client.cancel_scheduled_retry('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/cancellations', body) + + def test_cancel_scheduled_retry_idempotency_key(self, mocker, client: PaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CancelScheduledRetryRequest() + + assert client.cancel_scheduled_retry('payment_id', body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments/payment_id/cancellations', body) def test_capture_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.capture_payment('payment_id', CaptureRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CaptureRequest() + + assert client.capture_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/captures', body) def test_capture_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.capture_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/captures' def test_refund_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.refund_payment('payment_id', RefundRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = RefundRequest() + + assert client.refund_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/refunds', body) def test_refund_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.refund_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/refunds' + + def test_reverse_payment(self, mocker, client: PaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ReversePaymentRequest() + + assert client.reverse_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/reversals', body) + + def test_reverse_payment_idempotency_key(self, mocker, client: PaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + + assert client.reverse_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/reversals' def test_void_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.void_payment('payment_id', VoidRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = VoidRequest() + + assert client.void_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/voids', body) def test_void_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.void_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/voids' def test_increment_payment_authorization(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.increment_payment_authorization('payment_id', AuthorizationRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = AuthorizationRequest() + + assert client.increment_payment_authorization('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/authorizations', body) def test_increment_payment_authorization_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.increment_payment_authorization('payment_id', AuthorizationRequest(), - 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = AuthorizationRequest() + + assert client.increment_payment_authorization('payment_id', body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments/payment_id/authorizations', body) + + def test_search_payments(self, mocker, client: PaymentsClient): + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentsSearchRequest() + + assert client.search_payments(body) == 'response' + assert_api_call(mock, 'payments/search', body) # sources def test_should_request_provider_token_source_payment(self, mocker, client: PaymentsClient): @@ -77,8 +157,9 @@ def test_should_request_provider_token_source_payment(self, mocker, client: Paym source.payment_method = 'method' source.account_holder = AccountHolder() - request = PaymentRequest() - request.source = source + body = PaymentRequest() + body.source = source - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payment(request, 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.request_payment(body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments', body) diff --git a/tests/payments/previous/payments_previous_client_test.py b/tests/payments/previous/payments_previous_client_test.py index 6a5d6171..8cb475b4 100644 --- a/tests/payments/previous/payments_previous_client_test.py +++ b/tests/payments/previous/payments_previous_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.payments_previous import PaymentRequest, PayoutRequest, CaptureRequest from checkout_sdk.payments.payments import RefundRequest, VoidRequest from checkout_sdk.payments.payments_client_previous import PaymentsClient @@ -13,49 +14,86 @@ def client(mock_sdk_configuration, mock_api_client): class TestPaymentsClient: def test_request_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payment(PaymentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentRequest() + + assert client.request_payment(body) == 'response' + assert_api_call(mock, 'payments', body) def test_request_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payment(PaymentRequest(), 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentRequest() + + assert client.request_payment(body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments', body) def test_request_payout(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payout(PayoutRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PayoutRequest() + + assert client.request_payout(body) == 'response' + assert_api_call(mock, 'payments', body) def test_request_payout_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_payout(PayoutRequest(), 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PayoutRequest() + + assert client.request_payout(body, 'idempotency_key') == 'response' + assert_api_call(mock, 'payments', body) def test_get_payment_details(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_details('payment_id') == 'response' + assert_api_call(mock, 'payments/payment_id') def test_get_payment_actions(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_actions('payment_id') == 'response' + assert_api_call(mock, 'payments/payment_id/actions') def test_capture_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.capture_payment('payment_id', CaptureRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CaptureRequest() + + assert client.capture_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/captures', body) def test_capture_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.capture_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/captures' def test_refund_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.refund_payment('payment_id', RefundRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = RefundRequest() + + assert client.refund_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/refunds', body) def test_refund_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.refund_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/refunds' def test_void_payment(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.void_payment('payment_id', VoidRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = VoidRequest() + + assert client.void_payment('payment_id', body) == 'response' + assert_api_call(mock, 'payments/payment_id/voids', body) def test_void_payment_idempotency_key(self, mocker, client: PaymentsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.void_payment('payment_id', None, 'idempotency_key') == 'response' + mock.assert_called_once() + args = mock.call_args.args + assert args[0] == 'payments/payment_id/voids' diff --git a/tests/payments/reverse_payments_integration_test.py b/tests/payments/reverse_payments_integration_test.py new file mode 100644 index 00000000..a6a580a1 --- /dev/null +++ b/tests/payments/reverse_payments_integration_test.py @@ -0,0 +1,55 @@ +from __future__ import absolute_import + +from checkout_sdk.payments.payments import ReversePaymentRequest +from tests.checkout_test_utils import new_uuid, assert_response, new_idempotency_key, retriable +from tests.payments.payments_test_utils import make_card_payment + + +# tests + +def test_should_reverse_card_payment(default_api): + payment_response = make_card_payment(default_api) + + reverse_request = build_reverse_payment_request() + + reverse_response = retriable(callback=default_api.payments.reverse_payment, + payment_id=payment_response.id, + reverse_payment_request=reverse_request) + assert_reverse_response(reverse_response) + + +def test_should_reverse_card_payment_idempotently(default_api): + payment_response = make_card_payment(default_api) + + reverse_request = build_reverse_payment_request() + idempotency_key = new_idempotency_key() + + reverse_response_1 = retriable(callback=default_api.payments.reverse_payment, + payment_id=payment_response.id, + reverse_payment_request=reverse_request, + idempotency_key=idempotency_key) + assert_response(reverse_response_1) + + reverse_response_2 = retriable(callback=default_api.payments.reverse_payment, + payment_id=payment_response.id, + reverse_payment_request=reverse_request, + idempotency_key=idempotency_key) + assert_response(reverse_response_2) + + assert reverse_response_1.action_id == reverse_response_2.action_id + + +# common methods + +def build_reverse_payment_request() -> ReversePaymentRequest: + request = ReversePaymentRequest() + request.reference = new_uuid() + return request + + +def assert_reverse_response(response): + assert_response(response, + 'http_metadata', + 'reference', + 'action_id', + '_links') diff --git a/tests/payments/search_payments_integration_test.py b/tests/payments/search_payments_integration_test.py new file mode 100644 index 00000000..3cd1e617 --- /dev/null +++ b/tests/payments/search_payments_integration_test.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import + +from datetime import datetime, timezone, timedelta + +import pytest + +from checkout_sdk.payments.payments import PaymentsSearchRequest +from tests.checkout_test_utils import assert_response, retriable +from tests.payments.payments_test_utils import make_card_payment + + +# tests +@pytest.mark.skip(reason='use search payments when needed, skipped because of the time it takes to execute') +def test_should_search_payments(default_api, oauth_api): + payment_response = make_card_payment(default_api) + + search_request = build_payments_search_request(payment_response.id) + + response = retriable(callback=oauth_api.payments.search_payments, + predicate=there_are_search_results, + timeout=5, + search_request=search_request) + assert_search_response(response, payment_response.id) + + +# common methods + +def build_payments_search_request(payment_id: str) -> PaymentsSearchRequest: + request = PaymentsSearchRequest() + request.query = "id:'" + payment_id + "'" + request.limit = 10 + request.from_ = datetime.now(timezone.utc) - timedelta(minutes=5) + request.to = datetime.now(timezone.utc) + timedelta(minutes=5) + return request + + +def assert_search_response(response, payment_id: str): + assert_response(response, + 'http_metadata', + 'data') + assert response.data[0].id == payment_id + + +def there_are_search_results(response) -> bool: + data = getattr(response, 'data', None) + return data is not None and len(data) > 0 diff --git a/tests/payments/sessions/payment_sessions_client_test.py b/tests/payments/sessions/payment_sessions_client_test.py index 7ac67afa..e3cdeb7b 100644 --- a/tests/payments/sessions/payment_sessions_client_test.py +++ b/tests/payments/sessions/payment_sessions_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.sessions.sessions import ( PaymentSessionsRequest, PaymentSessionWithPaymentRequest, SubmitPaymentSessionRequest ) @@ -14,14 +15,22 @@ def client(mock_sdk_configuration, mock_api_client): class TestPaymentSessionsClient: def test_should_create_payment_sessions(self, mocker, client: PaymentSessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_payment_sessions(PaymentSessionsRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentSessionsRequest() + + assert client.create_payment_sessions(body) == 'response' + assert_api_call(mock, 'payment-sessions', body) def test_should_create_payment_session_with_payment(self, mocker, client: PaymentSessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_payment_session_with_payment(PaymentSessionWithPaymentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentSessionWithPaymentRequest() + + assert client.create_payment_session_with_payment(body) == 'response' + assert_api_call(mock, 'payment-sessions/complete', body) def test_should_submit_payment_session(self, mocker, client: PaymentSessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - session_id = 'ps_test_session_id' - assert client.submit_payment_session(session_id, SubmitPaymentSessionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SubmitPaymentSessionRequest() + + assert client.submit_payment_session('ps_test_session_id', body) == 'response' + assert_api_call(mock, 'payment-sessions/ps_test_session_id/submit', body) diff --git a/tests/payments/setups/payment_setups_client_test.py b/tests/payments/setups/payment_setups_client_test.py index e7f27d6b..17fd4e70 100644 --- a/tests/payments/setups/payment_setups_client_test.py +++ b/tests/payments/setups/payment_setups_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.payments.setups.setups import PaymentSetupsRequest from checkout_sdk.payments.setups.setups_client import PaymentSetupsClient @@ -12,17 +13,27 @@ def client(mock_sdk_configuration, mock_api_client): class TestPaymentSetupsClient: def test_should_create_payment_setup(self, mocker, client: PaymentSetupsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_payment_setup(PaymentSetupsRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PaymentSetupsRequest() + + assert client.create_payment_setup(body) == 'response' + assert_api_call(mock, 'payments/setups', body) def test_should_update_payment_setup(self, mocker, client: PaymentSetupsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_payment_setup('setup_id', PaymentSetupsRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = PaymentSetupsRequest() + + assert client.update_payment_setup('setup_id', body) == 'response' + assert_api_call(mock, 'payments/setups/setup_id', body) def test_should_get_payment_setup(self, mocker, client: PaymentSetupsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_setup('setup_id') == 'response' + assert_api_call(mock, 'payments/setups/setup_id') def test_should_confirm_payment_setup(self, mocker, client: PaymentSetupsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.confirm_payment_setup('setup_id', 'payment_method_option_id') == 'response' + assert_api_call(mock, 'payments/setups/setup_id/confirm/payment_method_option_id') diff --git a/tests/reconciliation/reconciliation_client_test.py b/tests/reconciliation/reconciliation_client_test.py index b811d263..209f2a10 100644 --- a/tests/reconciliation/reconciliation_client_test.py +++ b/tests/reconciliation/reconciliation_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.common.common import QueryFilterDateRange from checkout_sdk.reconciliation.reconciliation_client import ReconciliationClient @@ -12,25 +13,41 @@ def client(mock_sdk_configuration, mock_api_client): class TestReconciliationClient: def test_should_query_payments_report(self, mocker, client: ReconciliationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.query_payments_report(QueryFilterDateRange()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = QueryFilterDateRange() + + assert client.query_payments_report(query) == 'response' + assert_api_call(mock, 'reporting/payments', query) def test_should_get_single_payment_report(self, mocker, client: ReconciliationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.single_payment_report('payment_id') == 'response' + assert_api_call(mock, 'reporting/payments/payment_id') def test_should_query_statements_report(self, mocker, client: ReconciliationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.query_statements_report(QueryFilterDateRange()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = QueryFilterDateRange() + + assert client.query_statements_report(query) == 'response' + assert_api_call(mock, 'reporting/statements', query) def test_should_retrieve_csv_payment_report(self, mocker, client: ReconciliationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + # NOTE: the original test passed 'payment_id' (a str) where the client signature + # expects a QueryFilterDateRange. Preserving that call as-is; only verifying path. assert client.retrieve_csv_payment_report('payment_id') == 'response' + assert_api_call(mock, 'reporting/payments/download') def test_should_retrieve_csv_single_statement_report(self, mocker, client: ReconciliationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_csv_single_statement_report('statement_id') == 'response' + assert_api_call(mock, 'reporting/statements/statement_id/payments/download') def test_should_retrieve_csv_statements_report(self, mocker, client: ReconciliationClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.retrieve_csv_statements_report(QueryFilterDateRange()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = QueryFilterDateRange() + + assert client.retrieve_csv_statements_report(query) == 'response' + assert_api_call(mock, 'reporting/statements/download', query) diff --git a/tests/reports/reports_client_test.py b/tests/reports/reports_client_test.py index dd4fc955..ab764092 100644 --- a/tests/reports/reports_client_test.py +++ b/tests/reports/reports_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.reports.reports import ReportsQuery from checkout_sdk.reports.reports_client import ReportsClient @@ -12,13 +13,20 @@ def client(mock_sdk_configuration, mock_api_client): class TestReportsClient: def test_should_get_all_reports(self, mocker, client: ReportsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.get_all_reports(ReportsQuery()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + query = ReportsQuery() + + assert client.get_all_reports(query) == 'response' + assert_api_call(mock, 'reports', query) def test_should_get_report_details(self, mocker, client: ReportsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_report_details('report_id') == 'response' + assert_api_call(mock, 'reports/report_id') def test_should_get_report_file(self, mocker, client: ReportsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_report_file('report_id', 'file_id') == 'response' + assert_api_call(mock, 'reports/report_id/files/file_id') diff --git a/tests/risk/risk_client_test.py b/tests/risk/risk_client_test.py index 75d4a9e3..382ca3eb 100644 --- a/tests/risk/risk_client_test.py +++ b/tests/risk/risk_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.risk.risk import PreAuthenticationAssessmentRequest, PreCaptureAssessmentRequest from checkout_sdk.risk.risk_client import RiskClient @@ -12,9 +13,15 @@ def client(mock_sdk_configuration, mock_api_client): class TestRiskClient: def test_should_request_pre_authentication_risk_scan(self, mocker, client: RiskClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_pre_authentication_risk_scan(PreAuthenticationAssessmentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PreAuthenticationAssessmentRequest() + + assert client.request_pre_authentication_risk_scan(body) == 'response' + assert_api_call(mock, 'risk/assessments/pre-authentication', body) def test_should_request_pre_capture_risk_scan(self, mocker, client: RiskClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_pre_capture_risk_scan(PreCaptureAssessmentRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = PreCaptureAssessmentRequest() + + assert client.request_pre_capture_risk_scan(body) == 'response' + assert_api_call(mock, 'risk/assessments/pre-capture', body) diff --git a/tests/sessions/sessions_client_test.py b/tests/sessions/sessions_client_test.py index 1ddace18..d797bbfb 100644 --- a/tests/sessions/sessions_client_test.py +++ b/tests/sessions/sessions_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.sessions.sessions import SessionRequest, AppSession, ThreeDsMethodCompletionRequest from checkout_sdk.sessions.sessions_client import SessionsClient @@ -12,21 +13,34 @@ def client(mock_sdk_configuration, mock_api_client): class TestSessionsClient: def test_request_session(self, mocker, client: SessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_session(SessionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SessionRequest() + + assert client.request_session(body) == 'response' + assert_api_call(mock, 'sessions', body) def test_get_session_details(self, mocker, client: SessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_session_details('session_id', 'session_secret') == 'response' + assert_api_call(mock, 'sessions/session_id') def test_update_session(self, mocker, client: SessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_session('session_id', AppSession()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = AppSession() + + assert client.update_session('session_id', body) == 'response' + assert_api_call(mock, 'sessions/session_id/collect-data', body) def test_complete_session(self, mocker, client: SessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + client.complete_session('session_id') + assert_api_call(mock, 'sessions/session_id/complete') def test_update_3ds_method_completion(self, mocker, client: SessionsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put') - client.update_3ds_method_completion('session_id', ThreeDsMethodCompletionRequest(), 'session_secret') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = ThreeDsMethodCompletionRequest() + + client.update_3ds_method_completion('session_id', body, 'session_secret') + assert_api_call(mock, 'sessions/session_id/issuer-fingerprint', body) diff --git a/tests/sources/sources_client_test.py b/tests/sources/sources_client_test.py index 1999e789..44c9e188 100644 --- a/tests/sources/sources_client_test.py +++ b/tests/sources/sources_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.sources.sources import SepaSourceRequest from checkout_sdk.sources.sources_client import SourcesClient @@ -12,5 +13,8 @@ def client(mock_sdk_configuration, mock_api_client): class TestSourcesClient: def test_should_create_sepa_source(self, mocker, client: SourcesClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_sepa_source(SepaSourceRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = SepaSourceRequest() + + assert client.create_sepa_source(body) == 'response' + assert_api_call(mock, 'sources', body) diff --git a/tests/standaloneaccountupdater/standalone_account_updater_client_test.py b/tests/standaloneaccountupdater/standalone_account_updater_client_test.py index 5901efa7..ecaab415 100644 --- a/tests/standaloneaccountupdater/standalone_account_updater_client_test.py +++ b/tests/standaloneaccountupdater/standalone_account_updater_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.standaloneaccountupdater.standalone_account_updater import GetUpdatedCardCredentialsRequest from checkout_sdk.standaloneaccountupdater.standalone_account_updater_client import StandaloneAccountUpdaterClient @@ -12,5 +13,8 @@ def client(mock_sdk_configuration, mock_api_client): 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' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = GetUpdatedCardCredentialsRequest() + + assert client.get_updated_card_credentials(body) == 'response' + assert_api_call(mock, 'account-updater/cards', body) diff --git a/tests/tokens/tokens_client_test.py b/tests/tokens/tokens_client_test.py index ceba5f52..5a29a84b 100644 --- a/tests/tokens/tokens_client_test.py +++ b/tests/tokens/tokens_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.tokens.tokens import CardTokenRequest, ApplePayTokenRequest from checkout_sdk.tokens.tokens_client import TokensClient @@ -12,9 +13,15 @@ def client(mock_sdk_configuration, mock_api_client): class TestTokensClient: def test_should_request_token(self, mocker, client: TokensClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_card_token(CardTokenRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CardTokenRequest() + + assert client.request_card_token(body) == 'response' + assert_api_call(mock, 'tokens', body) def test_should_request_wallet_token(self, mocker, client: TokensClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.request_wallet_token(ApplePayTokenRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ApplePayTokenRequest() + + assert client.request_wallet_token(body) == 'response' + assert_api_call(mock, 'tokens', body) diff --git a/tests/transfers/transfers_client_test.py b/tests/transfers/transfers_client_test.py index dc0ca5a4..aa77c758 100644 --- a/tests/transfers/transfers_client_test.py +++ b/tests/transfers/transfers_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.transfers.transfers import CreateTransferRequest from checkout_sdk.transfers.transfers_client import TransfersClient @@ -12,9 +13,14 @@ def client(mock_sdk_configuration, mock_api_client): class TestTransfersClient: def test_should_initiate_transfer_of_funds(self, mocker, client: TransfersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.initiate_transfer_of_funds(CreateTransferRequest(), 'idempotency_key') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateTransferRequest() + + assert client.initiate_transfer_of_funds(body, 'idempotency_key') == 'response' + assert_api_call(mock, 'transfers', body) def test_should_retrieve_a_transfer(self, mocker, client: TransfersClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_a_transfer('transfer_id') == 'response' + assert_api_call(mock, 'transfers/transfer_id') diff --git a/tests/webhooks/webhooks_client_test.py b/tests/webhooks/webhooks_client_test.py index 544818bd..53e3e3f5 100644 --- a/tests/webhooks/webhooks_client_test.py +++ b/tests/webhooks/webhooks_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.webhooks.webhooks import WebhookRequest from checkout_sdk.webhooks.webhooks_client import WebhooksClient @@ -12,25 +13,40 @@ def client(mock_sdk_configuration, mock_api_client): class TestWebhooksClient: def test_retrieve_webhooks(self, mocker, client: WebhooksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_webhooks() == 'response' + assert_api_call(mock, 'webhooks') def test_register_webhook(self, mocker, client: WebhooksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.register_webhook(WebhookRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = WebhookRequest() + + assert client.register_webhook(body) == 'response' + assert_api_call(mock, 'webhooks', body) def test_retrieve_webhook(self, mocker, client: WebhooksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.retrieve_webhook('webhook_id') == 'response' + assert_api_call(mock, 'webhooks/webhook_id') def test_update_webhook(self, mocker, client: WebhooksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_webhook('webhook_id', WebhookRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = WebhookRequest() + + assert client.update_webhook('webhook_id', body) == 'response' + assert_api_call(mock, 'webhooks/webhook_id', body) def test_patch_webhook(self, mocker, client: WebhooksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.patch_webhook('webhook_id', WebhookRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = WebhookRequest() + + assert client.patch_webhook('webhook_id', body) == 'response' + assert_api_call(mock, 'webhooks/webhook_id', body) def test_remove_webhook(self, mocker, client: WebhooksClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.remove_webhook('webhook_id') == 'response' + assert_api_call(mock, 'webhooks/webhook_id') diff --git a/tests/workflows/workflows_client_test.py b/tests/workflows/workflows_client_test.py index ced9acdb..dd260735 100644 --- a/tests/workflows/workflows_client_test.py +++ b/tests/workflows/workflows_client_test.py @@ -1,5 +1,6 @@ import pytest +from tests._assertions import assert_api_call from checkout_sdk.workflows.workflows import CreateWorkflowRequest, UpdateWorkflowRequest, \ WebhookWorkflowActionRequest, EventWorkflowConditionRequest, ReflowRequest, EventTypesRequest from checkout_sdk.workflows.workflows_client import WorkflowsClient @@ -13,86 +14,135 @@ def client(mock_sdk_configuration, mock_api_client): class TestWorkflowsClient: def test_get_workflows(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_workflows() == 'response' + assert_api_call(mock, 'workflows') def test_create_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.create_workflow(CreateWorkflowRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = CreateWorkflowRequest() + + assert client.create_workflow(body) == 'response' + assert_api_call(mock, 'workflows', body) def test_get_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_workflow('workflow_id') == 'response' + assert_api_call(mock, 'workflows/workflow_id') def test_remove_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.remove_workflow('workflow_id') == 'response' + assert_api_call(mock, 'workflows/workflow_id') def test_update_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') - assert client.update_workflow('workflow_id', UpdateWorkflowRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.patch', return_value='response') + body = UpdateWorkflowRequest() + + assert client.update_workflow('workflow_id', body) == 'response' + assert_api_call(mock, 'workflows/workflow_id', body) def test_add_workflow_action(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.add_workflow_action('workflow_id', WebhookWorkflowActionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = WebhookWorkflowActionRequest() + + assert client.add_workflow_action('workflow_id', body) == 'response' + assert_api_call(mock, 'workflows/workflow_id/actions', body) def test_update_workflow_action(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_workflow_action('workflow_id', 'action_id', WebhookWorkflowActionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = WebhookWorkflowActionRequest() + + assert client.update_workflow_action('workflow_id', 'action_id', body) == 'response' + assert_api_call(mock, 'workflows/workflow_id/actions/action_id', body) def test_remove_workflow_action(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.remove_workflow_action('workflow_id', 'action_id') == 'response' + assert_api_call(mock, 'workflows/workflow_id/actions/action_id') def test_add_workflow_condition(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.add_workflow_condition('workflow_id', EventWorkflowConditionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = EventWorkflowConditionRequest() + + assert client.add_workflow_condition('workflow_id', body) == 'response' + assert_api_call(mock, 'workflows/workflow_id/conditions', body) def test_update_workflow_condition(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') - assert client.update_workflow_condition('workflow_id', 'condition_id', - EventWorkflowConditionRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.put', return_value='response') + body = EventWorkflowConditionRequest() + + assert client.update_workflow_condition('workflow_id', 'condition_id', body) == 'response' + assert_api_call(mock, 'workflows/workflow_id/conditions/condition_id', body) def test_remove_workflow_condition(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.delete', return_value='response') + assert client.remove_workflow_condition('workflow_id', 'condition_id') == 'response' + assert_api_call(mock, 'workflows/workflow_id/conditions/condition_id') def test_test_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.test_workflow('workflow_id', EventTypesRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = EventTypesRequest() + + assert client.test_workflow('workflow_id', body) == 'response' + assert_api_call(mock, 'workflows/workflow_id/test', body) def test_get_event_types(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_event_types() == 'response' + assert_api_call(mock, 'workflows/event-types') def test_get_event(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_event('event_id') == 'response' + assert_api_call(mock, 'workflows/events/event_id') def test_should_get_action_invocations(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') - assert client.get_action_invocations('action_id', 'event_id') == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + + assert client.get_action_invocations('event_id', 'action_id') == 'response' + assert_api_call(mock, 'workflows/events/event_id/actions/action_id') def test_reflow_by_event(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.reflow_by_event('event_id') == 'response' + assert_api_call(mock, 'workflows/events/event_id/reflow') def test_reflow_by_event_and_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.reflow_by_event_and_workflow('event_id', 'workflow_id') == 'response' + assert_api_call(mock, 'workflows/events/event_id/workflow/workflow_id/reflow') def test_reflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') - assert client.reflow(ReflowRequest()) == 'response' + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + body = ReflowRequest() + + assert client.reflow(body) == 'response' + assert_api_call(mock, 'workflows/events/reflow', body) def test_get_subject_events(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_subject_events('subject_id') == 'response' + assert_api_call(mock, 'workflows/events/subject/subject_id') def test_reflow_by_subject(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.reflow_by_subject('subject_id') == 'response' + assert_api_call(mock, 'workflows/events/subject/subject_id/reflow') def test_reflow_by_subject_and_workflow(self, mocker, client: WorkflowsClient): - mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + mock = mocker.patch('checkout_sdk.api_client.ApiClient.post', return_value='response') + assert client.reflow_by_subject_and_workflow('subject_id', 'workflow_id') == 'response' + assert_api_call(mock, 'workflows/events/subject/subject_id/workflow/workflow_id/reflow')