From b7ef744262d45703d53697791f8be11c3273de85 Mon Sep 17 00:00:00 2001 From: remleinpiotr Date: Fri, 9 Dec 2022 16:07:39 +0100 Subject: [PATCH 1/5] adding billing api --- livechat/billing/__init__.py | 2 + livechat/billing/api/__init__.py | 2 + livechat/billing/api/v1.py | 320 ++++++++++++++++++++++ livechat/billing/base.py | 47 ++++ livechat/tests/test_billing_api_client.py | 91 ++++++ 5 files changed, 462 insertions(+) create mode 100644 livechat/billing/__init__.py create mode 100644 livechat/billing/api/__init__.py create mode 100644 livechat/billing/api/v1.py create mode 100644 livechat/billing/base.py create mode 100644 livechat/tests/test_billing_api_client.py diff --git a/livechat/billing/__init__.py b/livechat/billing/__init__.py new file mode 100644 index 0000000..c55beea --- /dev/null +++ b/livechat/billing/__init__.py @@ -0,0 +1,2 @@ +# pylint: disable=C0114 +from livechat.billing.base import BillingApi diff --git a/livechat/billing/api/__init__.py b/livechat/billing/api/__init__.py new file mode 100644 index 0000000..1d9d913 --- /dev/null +++ b/livechat/billing/api/__init__.py @@ -0,0 +1,2 @@ +# pylint: disable=C0114 +from .v1 import BillingApiV1 diff --git a/livechat/billing/api/v1.py b/livechat/billing/api/v1.py new file mode 100644 index 0000000..5bda25f --- /dev/null +++ b/livechat/billing/api/v1.py @@ -0,0 +1,320 @@ +''' Billing API module with client class in version 1. ''' + +import httpx + +from livechat.utils.helpers import prepare_payload +from livechat.utils.http_client import HttpClient + + +class BillingApiV1(HttpClient): + ''' Billing API client class in version 1. ''' + def __init__(self, + token: str, + base_url: str, + http2: bool, + proxies=None, + verify: bool = True): + super().__init__(token, base_url, http2, proxies, verify) + self.api_url = f'https://{base_url}/v1' + + # direct_charge + + def create_direct_charge(self, + name: str = None, + price: int = None, + quantity: int = None, + return_url: str = None, + per_account: bool = None, + test: str = None, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Creates a new direct charge for the user (one time fee). + Args: + name (str): Name of the direct charge. + price (int): Price of the charge defined in cents. + quantity (int): Number of the accounts within the organization. + return_url (str): Redirection url for the client. + per_account (bool): Whether or not the app is sold in ppa account model. + test (str): Whether or not the direct charge is for test. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + return self.session.post(f'{self.api_url}/direct_charge', + json=payload, + headers=headers) + + def get_direct_charge(self, + charge_id: str, + params: dict = None, + headers: dict = None) -> httpx.Response: + ''' Returns specific direct charge. + Args: + charge_id (str): ID of the direct charge. + params (dict): Custom params to be used in request's query string. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if params is None: + params = prepare_payload(locals()) + del params['charge_id'] + return self.session.get(f'{self.api_url}/direct_charge/{charge_id}', + params=params, + headers=headers) + + def list_direct_charges(self, + page: int = None, + status: str = None, + params: dict = None, + headers: dict = None) -> httpx.Response: + ''' Lists all direct charges. + Args: + page (int): + status (str): + params (dict): Custom params to be used in request's query string. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if params is None: + params = prepare_payload(locals()) + return self.session.get(f'{self.api_url}/direct_charge', + params=params, + headers=headers) + + def activate_direct_charge(self, + charge_id: str, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Activates specific direct charge. + Args: + charge_id (str): ID of the direct charge. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + del payload['charge_id'] + return self.session.put(f'{self.api_url}/direct_charge/{charge_id}', + json=payload, + headers=headers) + +# ledger + + def get_ledger(self, + params: dict = None, + headers: dict = None) -> httpx.Response: + ''' Returns current ledger. + Args: + params (dict): Custom params to be used in request's query string. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if params is None: + params = prepare_payload(locals()) + return self.session.get(f'{self.api_url}/ledger', + params=params, + headers=headers) + + def get_ledger_balance(self, + params: dict = None, + headers: dict = None) -> httpx.Response: + ''' Returns current ledger balance. + Args: + params (dict): Custom params to be used in request's query string. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if params is None: + params = prepare_payload(locals()) + return self.session.get(f'{self.api_url}/ledger/balance', + params=params, + headers=headers) + + +# recurent_charge + + def create_recurrent_charge(self, + name: str = None, + price: int = None, + return_url: str = None, + per_account: bool = None, + trial_days: int = None, + months: int = None, + test: str = True, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Creates a new reccurent charge for the user (periodic payment). + Args: + name (str): Name of the reccurent charge. + price (int): Price of the charge defined in cents. + return_url (str): Redirection url for the client. + per_account (bool): Whether or not the app is sold in ppa account model. + trial_days (int): Number of granted trial days. + months (int): Charge frequency expressed in months. + test (str): Whether or not the direct charge is for test. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + return self.session.post(f'{self.api_url}/recurrent_charge', + json=payload, + headers=headers) + + def get_recurrent_charge(self, + charge_id: str, + params: dict = None, + headers: dict = None) -> httpx.Response: + ''' Gets specific reccurent charge. + Args: + charge_id (str): ID of the direct charge. + params (dict): Custom params to be used in request's query string. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if params is None: + params = prepare_payload(locals()) + del params['charge_id'] + return self.session.get(f'{self.api_url}/recurrent_charge/{charge_id}', + params=params, + headers=headers) + + def accept_recurrent_charge(self, + charge_id: str, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Accpets specific reccurent charge. + Args: + charge_id (str): ID of the direct charge. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + del payload['charge_id'] + return self.session.put( + f'{self.api_url}/recurrent_charge/{charge_id}/accept', + json=payload, + headers=headers) + + def decline_recurrent_charge(self, + charge_id: str, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Declines specific reccurent charge. + Args: + charge_id (str): ID of the direct charge. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + del payload['charge_id'] + return self.session.put( + f'{self.api_url}/recurrent_charge/{charge_id}/decline', + json=payload, + headers=headers) + + def activate_recurrent_charge(self, + charge_id: str, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Activates specific reccurent charge. + Args: + charge_id (str): ID of the direct charge. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + del payload['charge_id'] + return self.session.put( + f'{self.api_url}/recurrent_charge/{charge_id}/activate', + json=payload, + headers=headers) + + def cancel_recurrent_charge(self, + charge_id: str, + payload: dict = None, + headers: dict = None) -> httpx.Response: + ''' Cancels specific reccurent charge. + Args: + charge_id (str): ID of the direct charge. + payload (dict): Custom payload to be used as request's data. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if payload is None: + payload = prepare_payload(locals()) + del payload['charge_id'] + return self.session.put( + f'{self.api_url}/recurrent_charge/{charge_id}/cancel', + json=payload, + headers=headers) diff --git a/livechat/billing/base.py b/livechat/billing/base.py new file mode 100644 index 0000000..8501bb0 --- /dev/null +++ b/livechat/billing/base.py @@ -0,0 +1,47 @@ +''' Module with base class that allows retrieval of client for specific + Billing API version. ''' + +# pylint: disable=W0613,W0622,C0103,R0913,R0903 + +from __future__ import annotations + +from .api import BillingApiV1 + + +class BillingApi: + ''' Base class that allows retrieval of client for specific + Billing API version. ''' + @staticmethod + def get_client(token: str, + version: str = 'v1', + base_url: str = 'billing.livechatinc.com', + http2: bool = False, + proxies: dict = None, + verify: bool = True) -> BillingApiV1: + ''' Returns client for specific Billing API version. + + Args: + token (str): Full token with type Bearer that will be + used as `Authorization` header in requests to API. + version (str): API's version. Defaults to the v2 version of API. + base_url (str): API's base url. Defaults to API's production URL. + http2 (bool): A boolean indicating if HTTP/2 support should be + enabled. Defaults to `False`. + proxies (dict): A dictionary mapping proxy keys to proxy URLs. + verify (bool): SSL certificates (a.k.a CA bundle) used to + verify the identity of requested hosts. Either `True` (default CA bundle), + a path to an SSL certificate file, an `ssl.SSLContext`, or `False` + (which will disable verification). Defaults to `True`. + + Returns: + BillingApi: API client object for specified version. + + Raises: + ValueError: If the specified version does not exist. + ''' + client = { + 'v1': BillingApiV1(token, base_url, http2, proxies, verify), + }.get(version) + if not client: + raise ValueError('Provided version does not exist.') + return client diff --git a/livechat/tests/test_billing_api_client.py b/livechat/tests/test_billing_api_client.py new file mode 100644 index 0000000..fd23c85 --- /dev/null +++ b/livechat/tests/test_billing_api_client.py @@ -0,0 +1,91 @@ +''' Tests for Billing API client. ''' + +# pylint: disable=E1120,W0621 + +import pytest + +from livechat.billing.base import BillingApi + + +@pytest.fixture +def billing_api_client(): + ''' Fixture returning Reports API client. ''' + return BillingApi.get_client(token='test') + + +def test_get_client_without_args(): + ''' Test if TypeError raised without args. ''' + with pytest.raises(TypeError) as exception: + BillingApi.get_client() + assert str( + exception.value + ) == "get_client() missing 1 required positional argument: 'token'" + + +def test_get_client_without_access_token(): + ''' Test if TypeError raised without access_token. ''' + with pytest.raises(TypeError) as exception: + BillingApi.get_client(version='test') + assert str( + exception.value + ) == "get_client() missing 1 required positional argument: 'token'" + + +def test_get_client_with_non_existing_version(): + ''' Test if ValueError raised for non-existing version. ''' + with pytest.raises(ValueError) as exception: + BillingApi.get_client(token='test', version='test') + assert str(exception.value) == 'Provided version does not exist.' + + +def test_get_client_with_valid_args(billing_api_client): + ''' Test if production API URL is used and token is added to headers for valid args. ''' + assert billing_api_client.api_url == f'https://billing.livechatinc.com/v1' + assert billing_api_client.session.headers.get('Authorization') == 'test' + + +def test_send_request(billing_api_client): + ''' Test if it's possible to send a basic request via Billing API + client with arbitrary chosen method. ''' + assert billing_api_client.create_direct_charge().json() == { + 'error': + 'invalid_request', + 'error_description': + 'The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.' + } + + +def test_modify_header(billing_api_client): + ''' Test if Reports-api object header can be updated with custom value. ''' + assert 'test' not in billing_api_client.get_headers() + billing_api_client.modify_header({'test': '1234'}) + assert 'test' in billing_api_client.get_headers() + + +def test_remove_header(billing_api_client): + ''' Test if header can be removed from Billing-api object. ''' + billing_api_client.modify_header({'test2': '1234'}) + assert 'test2' in billing_api_client.get_headers() + billing_api_client.remove_header('test2') + assert 'test2' not in billing_api_client.get_headers() + + +def test_custom_headers_within_the_request(billing_api_client): + ''' Test if custom headers can be added to the session headers + only within the particular request. ''' + headers = {'x-test': 'enabled'} + response = billing_api_client.create_direct_charge(headers=headers) + assert headers.items() <= response.request.headers.items() + assert 'x-test' not in billing_api_client.get_headers() + + +def test_client_supports_http_1(): + ''' Test if client supports HTTP/1.1 protocol. ''' + client = BillingApi.get_client(token='test') + assert client.create_direct_charge().http_version == 'HTTP/1.1' + + +def test_client_supports_http_2(): + ''' Test if client supports HTTP/2 protocol. ''' + client = BillingApi.get_client(token='test', http2=True) + assert client.create_direct_charge().http_version == 'HTTP/2' From 75a369c5e59e97877bd5ffd9a476bc5034ba63f8 Mon Sep 17 00:00:00 2001 From: remleinpiotr Date: Thu, 15 Dec 2022 15:06:50 +0100 Subject: [PATCH 2/5] billing-api final changes --- livechat/billing/api/v1.py | 46 ++++++++++++++++++----- livechat/tests/test_billing_api_client.py | 11 +++--- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/livechat/billing/api/v1.py b/livechat/billing/api/v1.py index 5bda25f..1ac1fad 100644 --- a/livechat/billing/api/v1.py +++ b/livechat/billing/api/v1.py @@ -25,7 +25,7 @@ def create_direct_charge(self, quantity: int = None, return_url: str = None, per_account: bool = None, - test: str = None, + test: bool = None, payload: dict = None, headers: dict = None) -> httpx.Response: ''' Creates a new direct charge for the user (one time fee). @@ -77,12 +77,14 @@ def get_direct_charge(self, def list_direct_charges(self, page: int = None, status: str = None, + order_client_id: str = None, params: dict = None, headers: dict = None) -> httpx.Response: ''' Lists all direct charges. Args: - page (int): - status (str): + page (int): Navigate to page number. + status (str): Filter charges by status. One of pending, accepted, active, declined, processed, failed or success. + order_client_id (str): Filter by specific `order_client_id`. params (dict): Custom params to be used in request's query string. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -173,7 +175,7 @@ def create_recurrent_charge(self, per_account: bool = None, trial_days: int = None, months: int = None, - test: str = True, + test: bool = True, payload: dict = None, headers: dict = None) -> httpx.Response: ''' Creates a new reccurent charge for the user (periodic payment). @@ -206,7 +208,7 @@ def get_recurrent_charge(self, headers: dict = None) -> httpx.Response: ''' Gets specific reccurent charge. Args: - charge_id (str): ID of the direct charge. + charge_id (str): ID of the recurrent charge. params (dict): Custom params to be used in request's query string. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -223,13 +225,39 @@ def get_recurrent_charge(self, params=params, headers=headers) + def list_recurrent_charges(self, + page: int = None, + status: str = None, + order_client_id: str = None, + params: dict = None, + headers: dict = None) -> httpx.Response: + ''' Lists all recurrent charges. + Args: + page (int): Navigate to specific page number. + status (str): Filter charges by status. One of pending, accepted, active, declined, processed, failed or success. + order_client_id (str): Filter by specific `order_client_id`. + params (dict): Custom params to be used in request's query string. + It overrides all other parameters provided for the method. + headers (dict): Custom headers to be used with session headers. + They will be merged with session-level values that are set, + however, these method-level parameters will not be persisted across requests. + Returns: + httpx.Response: The Response object from `httpx` library, + which contains a server's response to an HTTP request. + ''' + if params is None: + params = prepare_payload(locals()) + return self.session.get(f'{self.api_url}/recurrent_charge', + params=params, + headers=headers) + def accept_recurrent_charge(self, charge_id: str, payload: dict = None, headers: dict = None) -> httpx.Response: ''' Accpets specific reccurent charge. Args: - charge_id (str): ID of the direct charge. + charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -253,7 +281,7 @@ def decline_recurrent_charge(self, headers: dict = None) -> httpx.Response: ''' Declines specific reccurent charge. Args: - charge_id (str): ID of the direct charge. + charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -277,7 +305,7 @@ def activate_recurrent_charge(self, headers: dict = None) -> httpx.Response: ''' Activates specific reccurent charge. Args: - charge_id (str): ID of the direct charge. + charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -301,7 +329,7 @@ def cancel_recurrent_charge(self, headers: dict = None) -> httpx.Response: ''' Cancels specific reccurent charge. Args: - charge_id (str): ID of the direct charge. + charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. diff --git a/livechat/tests/test_billing_api_client.py b/livechat/tests/test_billing_api_client.py index fd23c85..eb0eb2c 100644 --- a/livechat/tests/test_billing_api_client.py +++ b/livechat/tests/test_billing_api_client.py @@ -40,7 +40,7 @@ def test_get_client_with_non_existing_version(): def test_get_client_with_valid_args(billing_api_client): ''' Test if production API URL is used and token is added to headers for valid args. ''' - assert billing_api_client.api_url == f'https://billing.livechatinc.com/v1' + assert billing_api_client.api_url == 'https://billing.livechatinc.com/v1' assert billing_api_client.session.headers.get('Authorization') == 'test' @@ -85,7 +85,8 @@ def test_client_supports_http_1(): assert client.create_direct_charge().http_version == 'HTTP/1.1' -def test_client_supports_http_2(): - ''' Test if client supports HTTP/2 protocol. ''' - client = BillingApi.get_client(token='test', http2=True) - assert client.create_direct_charge().http_version == 'HTTP/2' +# At this point billing-api v1 support http2 but will always negotiate to use 'HTTP/1.1' +# def test_client_supports_http_2(): +# ''' Test if client supports HTTP/2 protocol. ''' +# client = BillingApi.get_client(token='test', http2=True) +# assert client.create_direct_charge().http_version == 'HTTP/2' From 58b71888ea69187fa5915303b2a85861632dc52f Mon Sep 17 00:00:00 2001 From: remleinpiotr Date: Thu, 15 Dec 2022 15:09:38 +0100 Subject: [PATCH 3/5] test corrected --- livechat/tests/test_billing_api_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/livechat/tests/test_billing_api_client.py b/livechat/tests/test_billing_api_client.py index eb0eb2c..bacdf09 100644 --- a/livechat/tests/test_billing_api_client.py +++ b/livechat/tests/test_billing_api_client.py @@ -9,7 +9,7 @@ @pytest.fixture def billing_api_client(): - ''' Fixture returning Reports API client. ''' + ''' Fixture returning Billing API client. ''' return BillingApi.get_client(token='test') @@ -56,14 +56,14 @@ def test_send_request(billing_api_client): def test_modify_header(billing_api_client): - ''' Test if Reports-api object header can be updated with custom value. ''' + ''' Test if Billing API object header can be updated with custom value. ''' assert 'test' not in billing_api_client.get_headers() billing_api_client.modify_header({'test': '1234'}) assert 'test' in billing_api_client.get_headers() def test_remove_header(billing_api_client): - ''' Test if header can be removed from Billing-api object. ''' + ''' Test if header can be removed from Billing API object. ''' billing_api_client.modify_header({'test2': '1234'}) assert 'test2' in billing_api_client.get_headers() billing_api_client.remove_header('test2') From a68aeb1329424f292ec01106644ea153fcc95f36 Mon Sep 17 00:00:00 2001 From: remleinpiotr Date: Thu, 12 Jan 2023 17:58:06 +0100 Subject: [PATCH 4/5] CR fixes --- changelog.md | 4 +++ livechat/billing/api/v1.py | 41 ++++++++++++----------- livechat/billing/base.py | 13 ++++--- livechat/config.py | 2 ++ livechat/tests/test_billing_api_client.py | 13 +++---- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/changelog.md b/changelog.md index 1743570..b63b460 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. ## [0.3.6] - TBA +### Added + +- Added support for billing-api. + ### Bugfixes - Fix `customer_monitoring_level` parameter in `login` method in agent-api v3.3/v3.4/v3.5 classes. diff --git a/livechat/billing/api/v1.py b/livechat/billing/api/v1.py index 1ac1fad..2a4a475 100644 --- a/livechat/billing/api/v1.py +++ b/livechat/billing/api/v1.py @@ -34,8 +34,8 @@ def create_direct_charge(self, price (int): Price of the charge defined in cents. quantity (int): Number of the accounts within the organization. return_url (str): Redirection url for the client. - per_account (bool): Whether or not the app is sold in ppa account model. - test (str): Whether or not the direct charge is for test. + per_account (bool): Whether or not the app is sold in ppa account model. Default: False. + test (str): Whether or not the direct charge is for test. Default: False. payload (dict): Custom payload to be used as request's data. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -82,7 +82,7 @@ def list_direct_charges(self, headers: dict = None) -> httpx.Response: ''' Lists all direct charges. Args: - page (int): Navigate to page number. + page (int): Navigate to page number. Default: 1. status (str): Filter charges by status. One of pending, accepted, active, declined, processed, failed or success. order_client_id (str): Filter by specific `order_client_id`. params (dict): Custom params to be used in request's query string. @@ -119,17 +119,20 @@ def activate_direct_charge(self, if payload is None: payload = prepare_payload(locals()) del payload['charge_id'] - return self.session.put(f'{self.api_url}/direct_charge/{charge_id}', - json=payload, - headers=headers) + return self.session.put( + f'{self.api_url}/direct_charge/{charge_id}/activate', + json=payload, + headers=headers) # ledger def get_ledger(self, + page: int = None, params: dict = None, headers: dict = None) -> httpx.Response: ''' Returns current ledger. Args: + page (int): Navigate to page number. Default: 1. params (dict): Custom params to be used in request's query string. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -148,7 +151,7 @@ def get_ledger(self, def get_ledger_balance(self, params: dict = None, headers: dict = None) -> httpx.Response: - ''' Returns current ledger balance. + ''' Returns current ledger balance in cents. Args: params (dict): Custom params to be used in request's query string. It overrides all other parameters provided for the method. @@ -178,15 +181,15 @@ def create_recurrent_charge(self, test: bool = True, payload: dict = None, headers: dict = None) -> httpx.Response: - ''' Creates a new reccurent charge for the user (periodic payment). + ''' Creates a new recurrent charge for the user (periodic payment). Args: - name (str): Name of the reccurent charge. + name (str): Name of the recurrent charge. price (int): Price of the charge defined in cents. return_url (str): Redirection url for the client. - per_account (bool): Whether or not the app is sold in ppa account model. - trial_days (int): Number of granted trial days. - months (int): Charge frequency expressed in months. - test (str): Whether or not the direct charge is for test. + per_account (bool): Whether or not the app is sold in ppa account model. Default: False. + trial_days (int): Number of granted trial days. Default: 0. + months (int): Charge frequency expressed in months. Default: 1. + test (str): Whether or not the direct charge is for test. Default: False. payload (dict): Custom payload to be used as request's data. It overrides all other parameters provided for the method. headers (dict): Custom headers to be used with session headers. @@ -206,7 +209,7 @@ def get_recurrent_charge(self, charge_id: str, params: dict = None, headers: dict = None) -> httpx.Response: - ''' Gets specific reccurent charge. + ''' Gets specific recurrent charge. Args: charge_id (str): ID of the recurrent charge. params (dict): Custom params to be used in request's query string. @@ -233,7 +236,7 @@ def list_recurrent_charges(self, headers: dict = None) -> httpx.Response: ''' Lists all recurrent charges. Args: - page (int): Navigate to specific page number. + page (int): Navigate to specific page number. Default: 1. status (str): Filter charges by status. One of pending, accepted, active, declined, processed, failed or success. order_client_id (str): Filter by specific `order_client_id`. params (dict): Custom params to be used in request's query string. @@ -255,7 +258,7 @@ def accept_recurrent_charge(self, charge_id: str, payload: dict = None, headers: dict = None) -> httpx.Response: - ''' Accpets specific reccurent charge. + ''' Accepets specific recurrent charge. Args: charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. @@ -279,7 +282,7 @@ def decline_recurrent_charge(self, charge_id: str, payload: dict = None, headers: dict = None) -> httpx.Response: - ''' Declines specific reccurent charge. + ''' Declines specific recurrent charge. Args: charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. @@ -303,7 +306,7 @@ def activate_recurrent_charge(self, charge_id: str, payload: dict = None, headers: dict = None) -> httpx.Response: - ''' Activates specific reccurent charge. + ''' Activates specific recurrent charge. Args: charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. @@ -327,7 +330,7 @@ def cancel_recurrent_charge(self, charge_id: str, payload: dict = None, headers: dict = None) -> httpx.Response: - ''' Cancels specific reccurent charge. + ''' Cancels specific recurrent charge. Args: charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data. diff --git a/livechat/billing/base.py b/livechat/billing/base.py index 8501bb0..c236621 100644 --- a/livechat/billing/base.py +++ b/livechat/billing/base.py @@ -5,16 +5,21 @@ from __future__ import annotations +from livechat.config import CONFIG + from .api import BillingApiV1 +billing_url = CONFIG.get('billing_url') +billing_version = CONFIG.get('billing_version') + class BillingApi: ''' Base class that allows retrieval of client for specific Billing API version. ''' @staticmethod def get_client(token: str, - version: str = 'v1', - base_url: str = 'billing.livechatinc.com', + version: str = billing_version, + base_url: str = billing_url, http2: bool = False, proxies: dict = None, verify: bool = True) -> BillingApiV1: @@ -23,7 +28,7 @@ def get_client(token: str, Args: token (str): Full token with type Bearer that will be used as `Authorization` header in requests to API. - version (str): API's version. Defaults to the v2 version of API. + version (str): Billing API's version. Defaults to the v1 version of Billing. base_url (str): API's base url. Defaults to API's production URL. http2 (bool): A boolean indicating if HTTP/2 support should be enabled. Defaults to `False`. @@ -40,7 +45,7 @@ def get_client(token: str, ValueError: If the specified version does not exist. ''' client = { - 'v1': BillingApiV1(token, base_url, http2, proxies, verify), + '1': BillingApiV1(token, base_url, http2, proxies, verify), }.get(version) if not client: raise ValueError('Provided version does not exist.') diff --git a/livechat/config.py b/livechat/config.py index 364b0ca..c7956e1 100644 --- a/livechat/config.py +++ b/livechat/config.py @@ -2,6 +2,8 @@ CONFIG = { 'url': 'api.livechatinc.com', + 'billing_url': 'billing.livechatinc.com', 'stable': '3.5', 'dev': '3.6', + 'billing_version': '1' } diff --git a/livechat/tests/test_billing_api_client.py b/livechat/tests/test_billing_api_client.py index bacdf09..94009e1 100644 --- a/livechat/tests/test_billing_api_client.py +++ b/livechat/tests/test_billing_api_client.py @@ -5,6 +5,10 @@ import pytest from livechat.billing.base import BillingApi +from livechat.config import CONFIG + +billing_url = CONFIG.get('billing_url') +billing_version = CONFIG.get('billing_version') @pytest.fixture @@ -40,7 +44,7 @@ def test_get_client_with_non_existing_version(): def test_get_client_with_valid_args(billing_api_client): ''' Test if production API URL is used and token is added to headers for valid args. ''' - assert billing_api_client.api_url == 'https://billing.livechatinc.com/v1' + assert billing_api_client.api_url == f'https://{billing_url}/v{billing_version}' assert billing_api_client.session.headers.get('Authorization') == 'test' @@ -83,10 +87,3 @@ def test_client_supports_http_1(): ''' Test if client supports HTTP/1.1 protocol. ''' client = BillingApi.get_client(token='test') assert client.create_direct_charge().http_version == 'HTTP/1.1' - - -# At this point billing-api v1 support http2 but will always negotiate to use 'HTTP/1.1' -# def test_client_supports_http_2(): -# ''' Test if client supports HTTP/2 protocol. ''' -# client = BillingApi.get_client(token='test', http2=True) -# assert client.create_direct_charge().http_version == 'HTTP/2' From 0fe05f9f353f65fef9692fd5a7a31d79b95a8326 Mon Sep 17 00:00:00 2001 From: remleinpiotr Date: Thu, 2 Feb 2023 08:58:24 +0100 Subject: [PATCH 5/5] typo in billing v1 corrected --- livechat/billing/api/v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livechat/billing/api/v1.py b/livechat/billing/api/v1.py index 2a4a475..d5b82b9 100644 --- a/livechat/billing/api/v1.py +++ b/livechat/billing/api/v1.py @@ -258,7 +258,7 @@ def accept_recurrent_charge(self, charge_id: str, payload: dict = None, headers: dict = None) -> httpx.Response: - ''' Accepets specific recurrent charge. + ''' Accepts specific recurrent charge. Args: charge_id (str): ID of the recurrent charge. payload (dict): Custom payload to be used as request's data.