diff --git a/.github/workflows/build-main.yml b/.github/workflows/build-main.yml index 26cd8ab9..4d6e8149 100644 --- a/.github/workflows/build-main.yml +++ b/.github/workflows/build-main.yml @@ -37,6 +37,7 @@ jobs: run: flake8 . --count --max-complexity=10 --max-line-length=127 --statistics - name: Run unit tests env: + CHECKOUT_PROCESSING_CHANNEL_ID: ${{ secrets.IT_CHECKOUT_PROCESSING_CHANNEL_ID }} CHECKOUT_PREVIOUS_SECRET_KEY: ${{ secrets.IT_CHECKOUT_PREVIOUS_SECRET_KEY }} CHECKOUT_PREVIOUS_PUBLIC_KEY: ${{ secrets.IT_CHECKOUT_PREVIOUS_PUBLIC_KEY }} CHECKOUT_DEFAULT_SECRET_KEY: ${{ secrets.IT_CHECKOUT_DEFAULT_SECRET_KEY }} diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 9e3259b9..12946d1b 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -38,6 +38,7 @@ jobs: run: pylint checkout_sdk - name: Run unit tests env: + CHECKOUT_PROCESSING_CHANNEL_ID: ${{ secrets.IT_CHECKOUT_PROCESSING_CHANNEL_ID }} CHECKOUT_PREVIOUS_SECRET_KEY: ${{ secrets.IT_CHECKOUT_PREVIOUS_SECRET_KEY }} CHECKOUT_PREVIOUS_PUBLIC_KEY: ${{ secrets.IT_CHECKOUT_PREVIOUS_PUBLIC_KEY }} CHECKOUT_DEFAULT_SECRET_KEY: ${{ secrets.IT_CHECKOUT_DEFAULT_SECRET_KEY }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index a921004a..8c14995e 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -32,6 +32,7 @@ jobs: pip install -r requirements-dev.txt - id: testing env: + CHECKOUT_PROCESSING_CHANNEL_ID: ${{ secrets.IT_CHECKOUT_PROCESSING_CHANNEL_ID }} CHECKOUT_PREVIOUS_SECRET_KEY: ${{ secrets.IT_CHECKOUT_PREVIOUS_SECRET_KEY }} CHECKOUT_PREVIOUS_PUBLIC_KEY: ${{ secrets.IT_CHECKOUT_PREVIOUS_PUBLIC_KEY }} CHECKOUT_DEFAULT_SECRET_KEY: ${{ secrets.IT_CHECKOUT_DEFAULT_SECRET_KEY }} diff --git a/checkout_sdk/checkout_api.py b/checkout_sdk/checkout_api.py index 3ae356d3..37198541 100644 --- a/checkout_sdk/checkout_api.py +++ b/checkout_sdk/checkout_api.py @@ -11,6 +11,7 @@ from checkout_sdk.checkout_apm_api import CheckoutApmApi from checkout_sdk.instruments.instruments_client import InstrumentsClient from checkout_sdk.issuing.issuing_client import IssuingClient +from checkout_sdk.payments.contexts.contexts_client import PaymentContextsClient from checkout_sdk.payments.hosted.hosted_payments_client import HostedPaymentsClient from checkout_sdk.payments.links.payments_client import PaymentsLinksClient from checkout_sdk.payments.payments_client import PaymentsClient @@ -64,3 +65,4 @@ def __init__(self, configuration: CheckoutConfiguration): self.card_metadata = CardMetadataClient(api_client=base_api_client, configuration=configuration) self.financial = FinancialClient(api_client=base_api_client, configuration=configuration) self.issuing = IssuingClient(api_client=base_api_client, configuration=configuration) + self.contexts = PaymentContextsClient(api_client=base_api_client, configuration=configuration) diff --git a/checkout_sdk/oauth_scopes.py b/checkout_sdk/oauth_scopes.py index 5605e5bd..9943af1b 100644 --- a/checkout_sdk/oauth_scopes.py +++ b/checkout_sdk/oauth_scopes.py @@ -49,3 +49,5 @@ class OAuthScopes(str, Enum): ISSUING_CARD_MGMT = 'issuing:card-mgmt' ISSUING_CONTROLS_READ = 'issuing:controls-read' ISSUING_CONTROLS_WRITE = 'issuing:controls-write' + + PAYMENT_CONTEXTS = 'Payment Contexts' diff --git a/checkout_sdk/payments/contexts/__init__.py b/checkout_sdk/payments/contexts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/checkout_sdk/payments/contexts/contexts.py b/checkout_sdk/payments/contexts/contexts.py new file mode 100644 index 00000000..78a513c2 --- /dev/null +++ b/checkout_sdk/payments/contexts/contexts.py @@ -0,0 +1,91 @@ +from datetime import datetime + +from checkout_sdk.common.common import Address +from checkout_sdk.common.enums import Currency, PaymentSourceType +from checkout_sdk.payments.payments import PaymentRequestSource, PaymentType, ShippingDetails, BillingPlan, \ + ShippingPreference, UserAction + + +class PaymentContextsPartnerCustomerRiskData: + key: str + value: str + + +class PaymentContextsTicket: + number: str + issue_date: datetime + issuing_carrier_code: str + travel_package_indicator: str + travel_agency_name: str + travel_agency_code: str + + +class PaymentContextsPassenger: + first_name: str + last_name: str + date_of_birth: datetime + address: Address + + +class PaymentContextsFlightLegDetails: + flight_number: str + carrier_code: str + class_of_travelling: str + departure_airport: str + departure_date: datetime + departure_time: str + arrival_airport: str + stop_over_code: str + fare_basis_code: str + + +class PaymentContextsAirlineData: + ticket: list # payment.contexts.PaymentContextsTicket + passenger: list # payment.contexts.PaymentContextsPassenger + flight_leg_details: list # payment.contexts.PaymentContextsFlightLegDetails + + +class PaymentContextsProcessing: + plan: BillingPlan + shipping_amount: int + invoice_id: str + brand_name: str + locale: str + shipping_preference: ShippingPreference + user_action: UserAction + partner_customer_risk_data: PaymentContextsPartnerCustomerRiskData + airline_data: list # payment.contexts.PaymentContextsAirlineData + + +class PaymentContextsItems: + name: str + quantity: int + unit_price: int + reference: str + total_amount: int + tax_amount: int + discount_amount: int + url: str + image_url: str + + +class PaymentContextsRequest: + source: PaymentRequestSource + amount: int + currency: Currency + payment_type: PaymentType + capture: bool + shipping: ShippingDetails + processing: PaymentContextsProcessing + processing_channel_id: str + reference: str + description: str + success_url: str + failure_url: str + items: list # payments.contexts.PaymentContextsItems + + +class PaymentContextPayPalSource(PaymentRequestSource): + + def __init__(self): + super().__init__(PaymentSourceType.PAYPAL) diff --git a/checkout_sdk/payments/contexts/contexts_client.py b/checkout_sdk/payments/contexts/contexts_client.py new file mode 100644 index 00000000..54cbd4e2 --- /dev/null +++ b/checkout_sdk/payments/contexts/contexts_client.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import + +from checkout_sdk.api_client import ApiClient +from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.checkout_configuration import CheckoutConfiguration +from checkout_sdk.client import Client +from checkout_sdk.payments.contexts.contexts import PaymentContextsRequest + + +class PaymentContextsClient(Client): + __PAYMENT_CONTEXTS_PATH = 'payment-contexts' + + def __init__(self, api_client: ApiClient, configuration: CheckoutConfiguration): + super().__init__(api_client=api_client, + configuration=configuration, + authorization_type=AuthorizationType.SECRET_KEY_OR_OAUTH) + + def create_payment_contexts(self, payment_contexts_request: PaymentContextsRequest): + return self._api_client.post(self.__PAYMENT_CONTEXTS_PATH, self._sdk_authorization(), + payment_contexts_request) + + def get_payment_context_details(self, payment_context_id: str): + return self._api_client.get(self.build_path(self.__PAYMENT_CONTEXTS_PATH, payment_context_id), + self._sdk_authorization()) diff --git a/checkout_sdk/payments/payments.py b/checkout_sdk/payments/payments.py index d0a5da58..bdbebf11 100644 --- a/checkout_sdk/payments/payments.py +++ b/checkout_sdk/payments/payments.py @@ -6,6 +6,7 @@ from checkout_sdk.common.common import AccountHolder, BankDetails, MarketplaceData, Address, Phone, CustomerRequest, \ AccountHolderIdentification from checkout_sdk.common.enums import PaymentSourceType, Currency, Country, AccountType, ChallengeIndicator +from checkout_sdk.sessions.sessions import DeliveryTimeframe class AuthorizationType(str, Enum): @@ -62,6 +63,7 @@ class Exemption(str, Enum): OUT_OF_SCA_SCOPE = 'out_of_sca_scope' OTHER = 'other' LOW_RISK_PROGRAM = 'low_risk_program' + DATA_SHARE = 'data_share' class ThreeDSFlowType(str, Enum): @@ -283,10 +285,23 @@ def __init__(self): super().__init__(PaymentSourceType.CUSTOMER) +class PaymentContextsShippingMethod(str, Enum): + DIGITAL = 'Digital' + PICK_UP = 'PickUp' + BILLING_ADDRESS = 'BillingAddress' + OTHER_ADDRESS = 'OtherAddress' + + class ShippingDetails: + first_name: str + last_name: str + email: str address: Address phone: Phone from_address_zip: str + timeframe: DeliveryTimeframe + method: PaymentContextsShippingMethod + delay: int class ThreeDsRequest: diff --git a/tests/payments/contexts/payment_contexts_client_test.py b/tests/payments/contexts/payment_contexts_client_test.py new file mode 100644 index 00000000..606a0a3c --- /dev/null +++ b/tests/payments/contexts/payment_contexts_client_test.py @@ -0,0 +1,20 @@ +import pytest + +from checkout_sdk.payments.contexts.contexts import PaymentContextsRequest +from checkout_sdk.payments.contexts.contexts_client import PaymentContextsClient + + +@pytest.fixture(scope='class') +def client(mock_sdk_configuration, mock_api_client): + return PaymentContextsClient(api_client=mock_api_client, configuration=mock_sdk_configuration) + + +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' + + def test_should_get_payment_context_details(self, mocker, client: PaymentContextsClient): + mocker.patch('checkout_sdk.api_client.ApiClient.get', return_value='response') + assert client.get_payment_context_details('payment_contexts_id') == 'response' diff --git a/tests/payments/contexts/payment_contexts_integration_test.py b/tests/payments/contexts/payment_contexts_integration_test.py new file mode 100644 index 00000000..72f82daa --- /dev/null +++ b/tests/payments/contexts/payment_contexts_integration_test.py @@ -0,0 +1,58 @@ +from __future__ import absolute_import + +import os + +from checkout_sdk.common.enums import Currency +from checkout_sdk.payments.contexts.contexts import PaymentContextsRequest, PaymentContextPayPalSource, \ + PaymentContextsItems +from checkout_sdk.payments.payments import PaymentType +from tests.checkout_test_utils import assert_response + + +def test_should_create_and_get_payment_context_details(default_api): + request = create_payment_contexts_request() + + response = default_api.contexts.create_payment_contexts(request) + + assert_response(response, + 'http_metadata', + 'id', + 'partner_metadata.order_id') + + payment_contexts_details = default_api.contexts.get_payment_context_details(response.id) + + assert_response(payment_contexts_details, + 'http_metadata', + 'payment_request', + 'payment_request.source', + 'payment_request.amount', + 'payment_request.currency', + 'payment_request.payment_type', + 'payment_request.capture', + 'payment_request.items', + 'payment_request.success_url', + 'payment_request.failure_url', + 'partner_metadata', + 'partner_metadata.order_id') + + +def create_payment_contexts_request(): + source = PaymentContextPayPalSource() + + items = PaymentContextsItems() + items.name = 'mask' + items.unit_price = 2000 + items.quantity = 1 + + request = PaymentContextsRequest() + request.source = source + request.amount = 2000 + request.currency = Currency.EUR + request.payment_type = PaymentType.REGULAR + request.capture = True + request.processing_channel_id = os.environ.get('CHECKOUT_PROCESSING_CHANNEL_ID') + request.success_url = 'https://example.com/payments/success' + request.failure_url = 'https://example.com/payments/failure' + request.items = [items] + + return request diff --git a/tests/payments/request_apm_payments_integration_test.py b/tests/payments/request_apm_payments_integration_test.py index b6a28f7f..d626f35a 100644 --- a/tests/payments/request_apm_payments_integration_test.py +++ b/tests/payments/request_apm_payments_integration_test.py @@ -353,6 +353,7 @@ def test_should_make_illicado_payment(default_api): payment_request=payment_request) +@pytest.mark.skip(reason='not available') def test_should_make_klarna_payment(default_api): account_holder = AccountHolder() account_holder.first_name = FIRST_NAME