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.