From e232b2678c46b80104112f8ac24ae152cbebf7bf Mon Sep 17 00:00:00 2001 From: mwoznowski Date: Wed, 17 May 2023 19:12:44 +0200 Subject: [PATCH 1/7] Refactor of SMS, DirectBilling methods and implement base client --- .gitignore | 7 +- examples/dcb.py | 18 ++ examples/sms.py | 15 ++ payments/directbilling.py | 128 ------------- payments/sms.py | 175 ------------------ requirements.txt | 3 +- setup.py | 13 +- simpay/__init__.py | 1 + simpay/__version__.py | 1 + simpay/baseModel.py | 37 ++++ simpay/directbilling/__init__.py | 0 simpay/directbilling/client.py | 91 +++++++++ simpay/directbilling/models.py | 143 ++++++++++++++ simpay/simpay.py | 73 ++++++++ simpay/sms/__init__.py | 0 simpay/sms/client.py | 67 +++++++ simpay/sms/models.py | 52 ++++++ simpay/sms_xml/__init__.py | 0 .../sms_xml.py => simpay/sms_xml/client.py | 30 +-- simpay/sms_xml/models.py | 21 +++ tests/sms_new.py | 28 +++ 21 files changed, 563 insertions(+), 340 deletions(-) create mode 100644 examples/dcb.py create mode 100644 examples/sms.py delete mode 100644 payments/directbilling.py delete mode 100644 payments/sms.py create mode 100644 simpay/__init__.py create mode 100644 simpay/__version__.py create mode 100644 simpay/baseModel.py create mode 100644 simpay/directbilling/__init__.py create mode 100644 simpay/directbilling/client.py create mode 100644 simpay/directbilling/models.py create mode 100644 simpay/simpay.py create mode 100644 simpay/sms/__init__.py create mode 100644 simpay/sms/client.py create mode 100644 simpay/sms/models.py create mode 100644 simpay/sms_xml/__init__.py rename payments/sms_xml.py => simpay/sms_xml/client.py (64%) create mode 100644 simpay/sms_xml/models.py create mode 100644 tests/sms_new.py diff --git a/.gitignore b/.gitignore index d25482b..343e6b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ venv/ .idea/ -*.iml \ No newline at end of file +.vscode +*.iml +dist +build +*__pycache__ +simpay_api.egg-info \ No newline at end of file diff --git a/examples/dcb.py b/examples/dcb.py new file mode 100644 index 0000000..aea4734 --- /dev/null +++ b/examples/dcb.py @@ -0,0 +1,18 @@ +from simpay import Client, ClientException + +apiKey = "" +apiPassword = "" +hashSign = "" +client = Client(apiKey, apiPassword) + +serviceID = "0d742940" +smsService = client.DirectBilling.get_service(serviceID) +smsServiceTransactions = client.DirectBilling.get_transactions(serviceID) + +try: + transaction = client.DirectBilling.generate_transaction(serviceID, hashSign, 1) + # Redirect customer to url from variable transaction.redirectUrl + # Optional validation BY specific number, client.SMS.verify_code(serviceID, 'CODE', NUMBER) + verified = client.DirectBilling.verify_transaction("...POST FIELDS from http as dict", hashSign) +except ClientException as error: + print(error.message) \ No newline at end of file diff --git a/examples/sms.py b/examples/sms.py new file mode 100644 index 0000000..4ad9c8d --- /dev/null +++ b/examples/sms.py @@ -0,0 +1,15 @@ +from simpay import Client, ClientException + +apiKey = "" +apiPassword = "" +client = Client(apiKey, apiPassword) + +serviceID = "0d742940" +smsService = client.SMS.get_service(serviceID) +smsServiceTransactions = client.SMS.get_transactions(serviceID) + +try: + transaction = client.SMS.verify_code(serviceID, 'CODE') + # Optional validation by specific number, client.SMS.verify_code(serviceID, 'CODE', NUMBER) +except ClientException as error: + print(error.message) \ No newline at end of file diff --git a/payments/directbilling.py b/payments/directbilling.py deleted file mode 100644 index 1103f08..0000000 --- a/payments/directbilling.py +++ /dev/null @@ -1,128 +0,0 @@ -from hashlib import sha256 -import json -import types -import requests - - -class DirectBilling: - def __init__(self, api_key, api_password): - self.api_key = api_key - self.api_password = api_password - self.url = 'https://api.simpay.pl/directbilling' - self.headers = { - 'X-SIM-KEY': self.api_key, - 'X-SIM-PASSWORD': self.api_password, - 'X-SIM-VERSION': '2.1.1', - 'X-SIM-PLATFORM': 'PYTHON', - } - - # https://docs.simpay.pl/pl/python/?python#directbilling-pobieranie-listy-uslug - def get_service_list(self): - result = [] - - r = requests.get(self.url + '/', headers=self.headers) - data = r.json() - - result.extend(data.data) - - while data.pagination.links.next_page is not None: - params = { 'page': data.pagination.current_page + 1 } - r = requests.get(self.url + '/', headers=self.headers, params=params) - data = r.json() - - result.extend(data.data) - - return r.json() - - def get_service_list_paginated(self, page=None, limit=None): - params = types.SimpleNamespace() - - if page: - params.page = page - if limit: - params.limit = limit - - r = requests.get(self.url + '/', headers=self.headers, params=params) - - return r.json() - - # https://docs.simpay.pl/pl/python/?python#directbilling-pobieranie-informacji-o-usludze - def get_service(self, service_id): - r = requests.get(self.url + '/' + service_id, headers=self.headers) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#directbilling-kalkulacja-prowizji - def calculate_commission(self, service_id, amount): - r = requests.get(self.url + '/' + service_id + '/calculate?amount=' + amount); - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#directbilling-pobieranie-listy-transakcji - def get_transaction_list(self, service_id): - result = [] - - r = requests.get(self.url + '/' + service_id + '/transactions', headers=self.headers) - data = r.json() - - result.extend(data.data) - - while data.pagination.links.next_page is not None: - params = { 'page': data.pagination.current_page + 1 } - r = requests.get(self.url + '/' + service_id + '/transactions', headers=self.headers, params=params) - data = r.json() - - result.extend(data.data) - - return r.json() - - def get_transaction_list_paginated(self, service_id, page=None, limit=None): - params = types.SimpleNamespace() - - if page: - params.page = page - if limit: - params.limit = limit - - r = requests.get(self.url + '/' + service_id + '/transactions', headers=self.headers, params=params) - - return r.json() - - # https://docs.simpay.pl/pl/python/?python#directbilling-pobieranie-informacji-o-transakcji - def get_transaction(self, service_id, transaction_id): - r = requests.get(self.url + '/' + service_id + '/transactions/' + transaction_id, headers=self.headers) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#directbilling-generowanie-transakcji - def create_transaction(self, service_id, key, request): - request.signature = self.generate_signature(key, request) - - r = requests.post(self.url + '/' + service_id + '/transactions', headers=self.headers, data=json.dumps(request)) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#directbilling-generowanie-transakcji - def check_notification(self, key, body): - if body.signature is None: - return False - - return body.signature == self.generate_signature(key, body) - - # https://docs.simpay.pl/pl/python/?python#directbilling-generowanie-transakcji - def generate_signature(self, key, data): - elements = [ - data.amount, - data.amountType, - data.description, - data.control - ] - - if data.returns is not None: - elements.append(data.returns.success) - elements.append(data.returns.failure) - - elements.append(data.phoneNumber) - elements.append(key) - - return sha256(bytes.fromhex([e for e in elements if e is not None].join('|'))).hexdigest() diff --git a/payments/sms.py b/payments/sms.py deleted file mode 100644 index 538cf1f..0000000 --- a/payments/sms.py +++ /dev/null @@ -1,175 +0,0 @@ -import json -import types -import requests - - -class SMS: - def __init__(self, api_key, api_password): - self.api_key = api_key - self.api_password = api_password - self.url = 'https://api.simpay.pl/sms' - self.headers = { - 'X-SIM-KEY': self.api_key, - 'X-SIM-PASSWORD': self.api_password, - 'X-SIM-VERSION': '2.1.1', - 'X-SIM-PLATFORM': 'PYTHON', - } - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-listy-uslug - def get_service_list(self): - result = [] - - r = requests.get(self.url, headers=self.headers) - data = r.json() - - result.extend(data.data) - - while data.pagination.links.next_page is not None: - params = { 'page': data.pagination.current_page + 1 } - r = requests.get(self.url, headers=self.headers, params=params) - data = r.json() - - result.extend(data.data) - - return r.json() - - def get_service_list_paginated(self, page=None, limit=None): - params = types.SimpleNamespace() - - if page: - params.page = page - if limit: - params.limit = limit - - r = requests.get(self.url, headers=self.headers, params=params) - - return r.json() - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-informacji-o-usludze - def get_service(self, service_id): - r = requests.get(self.url + '/' + service_id, headers=self.headers) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-listy-transakcji - def get_transaction_list(self, service_id): - result = [] - - r = requests.get(self.url + '/' + service_id + '/transactions', headers=self.headers) - data = r.json() - - result.extend(data.data) - - while data.pagination.links.next_page is not None: - params = { 'page': data.pagination.current_page + 1 } - r = requests.get(self.url + '/' + service_id + '/transactions', headers=self.headers, params=params) - data = r.json() - - result.extend(data.data) - - return r.json() - - def get_transaction_list_paginated(self, service_id, page=None, limit=None): - params = types.SimpleNamespace() - - if page: - params.page = page - if limit: - params.limit = limit - - r = requests.get(self.url + '/' + service_id + '/transactions', headers=self.headers, params=params) - - return r.json() - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-informacji-o-transakcji - def get_transaction(self, service_id, transaction_id): - r = requests.get(self.url + '/' + service_id + '/transactions/' + transaction_id, headers=self.headers) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-dostepnych-numerow-dla-uslugi - def get_service_numbers(self, service_id): - result = [] - - r = requests.get(self.url + '/sms/' + service_id + '/numbers', headers=self.headers) - data = r.json() - - result.extend(data.data) - - while data.pagination.links.next_page is not None: - params = { 'page': data.pagination.current_page + 1 } - r = requests.get(self.url + '/sms/' + service_id + '/numbers', headers=self.headers, params=params) - data = r.json() - - result.extend(data.data) - - return r.json() - - def get_service_numbers_paginated(self, service_id, page=None, limit=None): - params = types.SimpleNamespace() - - if page: - params.page = page - if limit: - params.limit = limit - - r = requests.get(self.url + '/sms/' + service_id + '/numbers', headers=self.headers, params=params) - - return r.json() - - # https://docs.simpay.pl/pl/python/?python#sms-informacji-o-pojedynczym-numerze-uslugi - def get_service_number(self, service_id, number): - r = requests.get(self.url + '/sms/' + service_id + '/numbers/' + number, headers=self.headers) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-wszystkich-dostepnych-numerow - def get_numbers(self): - result = [] - - r = requests.get(self.url + '/numbers', headers=self.headers) - data = r.json() - - result.extend(data.data) - - while data.pagination.links.next_page is not None: - params = { 'page': data.pagination.current_page + 1 } - r = requests.get(self.url + '/numbers', headers=self.headers, params=params) - data = r.json() - - result.extend(data.data) - - return r.json() - - def get_numbers_paginated(self, page=None, limit=None): - params = types.SimpleNamespace() - - if page: - params.page = page - if limit: - params.limit = limit - - r = requests.get(self.url + '/numbers', headers=self.headers, params=params) - - return r.json() - - # https://docs.simpay.pl/pl/python/?python#sms-pobieranie-pojedynczego-numeru-sms - - def get_number(self, number): - r = requests.get(self.url + '/numbers/' + number, headers=self.headers) - - return r.json().data - - # https://docs.simpay.pl/pl/python/?python#sms-weryfikacja-poprawnosci-kodu - - def verify_sms_code(self, service_id, code, number=None): - body = types.SimpleNamespace() - body.code = code - - if number: - body.number = number - - r = requests.post(self.url + '/' + service_id, headers=self.headers, data=json.dumps(body)) - - return r.json().data - diff --git a/requirements.txt b/requirements.txt index 0273e66..aaabdc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests~=2.26.0 -setuptools~=57.0.0 \ No newline at end of file +setuptools~=57.0.0 +pydantic~=1.10.7 \ No newline at end of file diff --git a/setup.py b/setup.py index caebea7..a874c8f 100644 --- a/setup.py +++ b/setup.py @@ -4,14 +4,13 @@ setup( name='simpay-api', - version='2.1.1', + version='3.0', description='Python wrapper for Simpay API', author='Rafał Więcek ', - url='https://github.com/SimPaypl/SimPay-API-Python', - - packages=['payments'], - install_required=[ - 'requests' - ] + python_requires=">3.0", + packages=find_packages(exclude=["tests*", "examples*"]), + test_suite="tests", + include_package_data=True, + install_requires=['requests'], ) \ No newline at end of file diff --git a/simpay/__init__.py b/simpay/__init__.py new file mode 100644 index 0000000..e6bb1d9 --- /dev/null +++ b/simpay/__init__.py @@ -0,0 +1 @@ +from .simpay import Client, ClientException \ No newline at end of file diff --git a/simpay/__version__.py b/simpay/__version__.py new file mode 100644 index 0000000..2fbfca3 --- /dev/null +++ b/simpay/__version__.py @@ -0,0 +1 @@ +VERSION = "3.0" \ No newline at end of file diff --git a/simpay/baseModel.py b/simpay/baseModel.py new file mode 100644 index 0000000..ccd8ee5 --- /dev/null +++ b/simpay/baseModel.py @@ -0,0 +1,37 @@ +from typing import TypeVar, Generic +from enum import Enum +from pydantic import BaseModel + +T = TypeVar('T') + + +class RequestMethod(Enum): + GET = 'get' + POST = 'post' + PUT = 'put' + HEAD = 'head' + DELETE = 'delete' + OPTIONS = 'options' + + +class ResponsePaginationLinks(BaseModel): + next_page: str | None + prev_page: str | None + + +class ResponsePagination(BaseModel): + total: int + count: int + per_page: int + current_page: int + total_pages: int + links: ResponsePaginationLinks + + +class Response(Generic[T]): + success: bool = False + data: T | None = None + pagination: ResponsePagination | None + + def has_pagination(self) -> bool: + return self.pagination is not None diff --git a/simpay/directbilling/__init__.py b/simpay/directbilling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simpay/directbilling/client.py b/simpay/directbilling/client.py new file mode 100644 index 0000000..47af4eb --- /dev/null +++ b/simpay/directbilling/client.py @@ -0,0 +1,91 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from simpay import Client + +from hashlib import sha256 +from simpay.baseModel import Response, RequestMethod +from simpay.directbilling.models import DCB_Service, DCB_Transaction, DCB_Service_CalculatedCommissions, DCB_Transaction_Returns, DCB_GeneratedTransaction, DCB_Amount_Type + + +class DirectBillingClient(object): + def __init__(self, client: 'Client') -> None: + self._client = client + + def get_services(self) -> list[DCB_Service]: + return [DCB_Service(**model) for model in self._client.requestAllPages(RequestMethod.GET, 'directbilling')] + + def get_services_paginate(self, page: int = 1, pageSize: int = 15) -> list[DCB_Service]: + return [DCB_Service(**model) for model in self._client.request(RequestMethod.GET, 'directbilling', options={ + 'page': page, + 'limit': pageSize + })['data']] + + def get_service(self, service_id: str) -> DCB_Service: + return DCB_Service(**self._client.request( + RequestMethod.GET, f'directbilling/{service_id}')['data']) + + def get_transactions(self, service_id: str) -> list[DCB_Transaction]: + return [DCB_Transaction(**model) for model in self._client.requestAllPages(RequestMethod.GET, + f'directbilling/{service_id}/transactions')] + + def get_transactions_paginate(self, service_id: str, page: int = 1, pageSize: int = 15) -> Response[DCB_Transaction]: + return [DCB_Transaction(**model) for model in self._client.request(RequestMethod.GET, + f'directbilling/{service_id}/transactions', options={ + 'page': page, + 'limit': pageSize + })['data']] + + def get_transaction(self, service_id: str, transaction_id: int) -> DCB_Transaction: + return DCB_Transaction(**self._client.request(RequestMethod.GET, + f'directbilling/{service_id}/transactions/{transaction_id}')['data']) + + def get_calculate(self, service_id: str, amount: float) -> list[DCB_Service_CalculatedCommissions]: + return DCB_Transaction(**self._client.request(RequestMethod.GET, + f'directbilling/{service_id}/calculate/', options={ + 'amount': amount + })['data']) + + def generate_transaction(self, service_id: str, hash: str, amount: float, amountType: DCB_Amount_Type = None, control: str = None, description: str = None, returns: DCB_Transaction_Returns = None, phoneNumber: str = None, steamid: str = None) -> DCB_GeneratedTransaction: + if not amountType: + amountType = DCB_Amount_Type.NET + fields = { + 'amount': amount, + 'amountType': amountType.value, + } + signatureBuffer = str(amount) + '|' + amountType.value + '|' + if description: + fields['description'] = description + signatureBuffer += description + '|' + if control: + fields['control'] = control + signatureBuffer += control + '|' + if returns: + fields['returns'] = returns + signatureBuffer += returns + '|' + if phoneNumber: + fields['phoneNumber'] = phoneNumber + signatureBuffer += phoneNumber + '|' + if steamid: + fields['steamid'] = steamid + signatureBuffer += steamid + '|' + signatureBuffer += hash + fields['signature'] = sha256( + signatureBuffer.encode('UTF-8')).hexdigest() + + return DCB_GeneratedTransaction(**self._client.request(RequestMethod.POST, + f'directbilling/{service_id}/transactions', fields)['data']) + + def verify_transaction(self, fields: dict, hash: str) -> bool: + signature = fields['signature'] + del fields['signature'] + signatureBuffer = [] + for value in fields.values(): + if isinstance(value, dict): + for fieldValue in value.values(): + signatureBuffer.append(str(fieldValue)) + else: + signatureBuffer.append(str(value)) + signatureBuffer.append(hash) + + return signature == sha256('|'.join(signatureBuffer).encode('UTF-8')).hexdigest() diff --git a/simpay/directbilling/models.py b/simpay/directbilling/models.py new file mode 100644 index 0000000..9e07f5f --- /dev/null +++ b/simpay/directbilling/models.py @@ -0,0 +1,143 @@ +from datetime import datetime +from enum import Enum +from pydantic import BaseModel + + +class DCB_Service_Status(Enum): + NEW = 'service_db_new' + ACTIVE = 'service_db_active' + BLOCKED = 'service_db_rejected' + DELETED = 'service_db_ongoing_registration' + + +class DCB_Transaction_Status(Enum): + NEW = 'transaction_db_new' + CONFIRMED = 'transaction_db_confirmed' + REJECTED = 'transaction_db_rejected' + CANCELED = 'transaction_db_canceled' + PAYED = 'transaction_db_payed' + GENERATE_ERROR = 'transaction_db_generate_error' + + +class DCB_Amount_Type(Enum): + NET = 'net' + GROSS = 'gross' + REQUIRED = 'required' + + +class DCB_Provider(Enum): + ORANGE = 1 + PLUS = 2 + PLAY = 3 + TMOBILE = 4 + + +class DCB_Service_Providers(BaseModel): + tmobile: bool + orange: bool + play: bool + plus: bool + + class Config: + fields = {'tmobile': 't-mobile'} + allow_population_by_field_name = True + + +class DCB_Service_MaxValues(BaseModel): + tmobile: str + orange: str + play: str + plus: str + + class Config: + fields = {'tmobile': 't-mobile'} + allow_population_by_field_name = True + + +class DCB_Service_Commission(BaseModel): + commission_0: str + commission_9: str + commission_25: str + + +class DCB_Service_Commissions(BaseModel): + tmobile: DCB_Service_Commission + orange: DCB_Service_Commission + play: DCB_Service_Commission + plus: DCB_Service_Commission + + class Config: + fields = {'tmobile': 't-mobile'} + allow_population_by_field_name = True + + +class DCB_Service_Values(BaseModel): + net: float + gross: float + partner: float | None + + +class DCB_Service_CalculatedCommissions(BaseModel): + tmobile: DCB_Service_Values | None + orange: DCB_Service_Values | None + play: DCB_Service_Values | None + plus: DCB_Service_Values | None + + class Config: + fields = {'tmobile': 't-mobile'} + allow_population_by_field_name = True + + +class DCB_Service_API(BaseModel): + complete: str + failure: str + + +class DCB_Service(BaseModel): + id: str + name: str + suffix: str + status: DCB_Service_Status + api: DCB_Service_API + providers: DCB_Service_Providers + commissions: DCB_Service_Commissions + maxValues: DCB_Service_MaxValues + created_at: datetime + + +class DCB_Transaction_Notify(BaseModel): + is_send: bool + last_send_at: datetime + count: int + + +class DCB_Transaction_Returns(BaseModel): + complete: str + failure: str + + +class DCB_Transaction(BaseModel): + id: str + status: DCB_Transaction_Status + phoneNumber: str | None + control: str | None + value: float | None + value_netto: float | None + values: DCB_Service_Values | None + operator: str | None + returns: DCB_Transaction_Returns | None + notify: DCB_Transaction_Notify | None + control: str | None + provider: int | None + signature: str | None + created_at: datetime | None + updated_at: datetime | None + + class Config: + fields = {'phoneNumber': 'number_from'} + allow_population_by_field_name = True + + +class DCB_GeneratedTransaction(BaseModel): + transactionId: str + redirectUrl: str diff --git a/simpay/simpay.py b/simpay/simpay.py new file mode 100644 index 0000000..d983213 --- /dev/null +++ b/simpay/simpay.py @@ -0,0 +1,73 @@ +from .__version__ import VERSION +from simpay.baseModel import RequestMethod, Response +from simpay.sms.client import SMSClient +from simpay.directbilling.client import DirectBillingClient +import requests + + +class ClientException(Exception): + def __init__(self, code=200, message=None, details=None): + self.code = code + self.message = message + self.details = details + + def __str__(self): + return self.message + + +class Client(object): + _version = VERSION + _user_agent = 'simpay-python-api' + _base_endpoint = 'https://api.simpay.pl/' + + def __init__( + self, + api_key: str, + api_password: str | None, + timeout: int = 5 + ) -> None: + self.api_key = api_key + self.api_password = api_password + self.timeout = timeout + + self._http_client = requests.Session() + self._http_client.mount(self._base_endpoint, + requests.adapters.HTTPAdapter()) + self._http_client.headers['accept'] = 'application/json' + self._http_client.headers['content-type'] = 'application/json' + self._http_client.headers['user-agent'] = self._user_agent + self._http_client.headers['X-SIM-KEY'] = self.api_key + + if self.api_password is not None: + self._http_client.headers['X-SIM-PASSWORD'] = self.api_password + + self.SMS: SMSClient = SMSClient(self) + self.DirectBilling: DirectBillingClient = DirectBillingClient(self) + + def request(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> Response: + response = self._http_client.request( + method.value, self._base_endpoint + uri, params=options, json=fields, headers=headers) + if len(response.content) == 0: + raise ClientException(response.status_code) + if not response.ok: + responseBody = response.json() + if responseBody['message']: + raise ClientException(response.status_code, responseBody['message'], responseBody['errors']) + else: + raise ClientException(response.status_code) + return response.json() + + def requestAllPages(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> object: + response = self.request(method, uri, fields, headers, options) + responseData = [] + if response['success']: + responseData = responseData + response['data'] + while response['pagination']['links']['next_page']: + response = self.request( + method, uri, fields, headers, options.update({ + 'page': response['pagination']['current_page'] + 1 + })) + if response['success']: + responseData = responseData + response['data'] + + return responseData diff --git a/simpay/sms/__init__.py b/simpay/sms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simpay/sms/client.py b/simpay/sms/client.py new file mode 100644 index 0000000..f3ba4f4 --- /dev/null +++ b/simpay/sms/client.py @@ -0,0 +1,67 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from simpay import Client + +from simpay.baseModel import Response, RequestMethod +from simpay.sms.models import SMS_Service, SMS_Transaction, SMS_Number + + +class SMSClient(object): + def __init__(self, client: 'Client') -> None: + self._client = client + + def get_services(self) -> list[SMS_Service]: + return [SMS_Service(**model) for model in self._client.requestAllPages(RequestMethod.GET, 'sms')] + + def get_services_paginate(self, page: int = 1, pageSize: int = 15) -> list[SMS_Service]: + return [SMS_Service(**model) for model in self._client.request(RequestMethod.GET, 'sms', options={ + 'page': page, + 'limit': pageSize + })['data']] + + def get_service(self, service_id: str) -> SMS_Service: + return SMS_Service(**self._client.request( + RequestMethod.GET, f'sms/{service_id}')['data']) + + def get_transactions(self, service_id: str) -> list[SMS_Transaction]: + return [SMS_Transaction(**model) for model in self._client.requestAllPages(RequestMethod.GET, + f'sms/{service_id}/transactions')] + + def get_transactions_paginate(self, service_id: str, page: int = 1, pageSize: int = 15) -> Response[SMS_Transaction]: + return [SMS_Transaction(**model) for model in self._client.request(RequestMethod.GET, + f'sms/{service_id}/transactions', options={ + 'page': page, + 'limit': pageSize + })['data']] + + def get_transaction(self, service_id: str, transaction_id: int) -> SMS_Transaction: + return SMS_Transaction(**self._client.request(RequestMethod.GET, + f'sms/{service_id}/transactions/{transaction_id}')['data']) + + def get_service_numbers(self, service_id: str) -> list[SMS_Number]: + return [SMS_Number(**model) for model in self._client.requestAllPages(RequestMethod.GET, + f'sms/{service_id}/numbers')] + + def get_service_number(self, service_id: str, number: int) -> SMS_Number: + return SMS_Number(**self._client.request(RequestMethod.GET, + f'sms/{service_id}/numbers/{number}')['data']) + + def get_numbers(self) -> list[SMS_Number]: + return [SMS_Number(**model) for model in self._client.requestAllPages(RequestMethod.GET, + 'sms/numbers')] + + def get_number(self, number: int) -> SMS_Number: + return SMS_Number(**self._client.request(RequestMethod.GET, + f'sms/numbers/{number}')['data']) + + def verify_code(self, service_id: str, code: str, number: int = None) -> SMS_Transaction: + fields = { + 'code': code, + } + + if number: + fields['number'] = number + + return SMS_Transaction(**self._client.request(RequestMethod.POST, + f'sms/{service_id}', fields)['data']) diff --git a/simpay/sms/models.py b/simpay/sms/models.py new file mode 100644 index 0000000..2c523df --- /dev/null +++ b/simpay/sms/models.py @@ -0,0 +1,52 @@ +from datetime import datetime +from enum import Enum +from pydantic import BaseModel + + +class SMS_Service_Status(Enum): + NEW = 'service_new' + ACTIVE = 'service_active' + BLOCKED = 'service_blocked' + DELETED = 'service_deleted' + SECOND_VERIFY = 'service_second_verify' + REJECTED = 'service_rejected' + VERIFY = 'service_verify' + ONGOING_REGISTRATION = 'service_ongoing_registration' + + +class SMS_Service_Type(Enum): + ONE_TIME_CODE = 'ONE_TIME_CODE' + CODE_PACK = 'CODE_PACK' + API_URL = 'API_URL' + + +class SMS_Service(BaseModel): + id: str + type: SMS_Service_Type + status: SMS_Service_Status + name: str + prefix: str + suffix: str + description: str | None + adult: bool = False + numbers: list[int] | None + created_at: datetime + + +class SMS_Transaction(BaseModel): + id: str + from_number: int + code: str + used: bool + send_at: datetime + + class Config: + fields = {'from_number': 'from'} + allow_population_by_field_name = True + + +class SMS_Number(BaseModel): + number: int + value: float + value_gross: float + adult: bool diff --git a/simpay/sms_xml/__init__.py b/simpay/sms_xml/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/payments/sms_xml.py b/simpay/sms_xml/client.py similarity index 64% rename from payments/sms_xml.py rename to simpay/sms_xml/client.py index 47b63b4..c1d6767 100644 --- a/payments/sms_xml.py +++ b/simpay/sms_xml/client.py @@ -2,22 +2,20 @@ import random import string import unicodedata +from simpay.sms_xml.models import PARAMS class SMS_XML: def __init__(self, api_key): self.api_key = api_key - # https://docs.simpay.pl/pl/python/?python#smsxml-odbieranie-informacji-o-sms def check_parameters(self, request): - for param in SMS_XML.PARAMS: + for param in PARAMS: if request.get(param) is None: return False return request.get("sign") is not None and request.get("sign") == self.sign(request) - # https://docs.simpay.pl/pl/python/?python#smsxml-odbieranie-informacji-o-sms - @staticmethod def generate_code(): key = '' @@ -26,8 +24,6 @@ def generate_code(): return key - # https://docs.simpay.pl/pl/python/?python#smsxml-odbieranie-informacji-o-sms - @staticmethod def generate_xml(code): header = "" footer = "" @@ -45,25 +41,3 @@ def sign(self, request): str(self.api_key), encoding="utf8") ).hexdigest() - - -SMS_XML.CODES = { - "7055": 0.25, - "7136": 0.5, - "7255": 1.0, - "7355": 1.5, - "7455": 2.0, - "7555": 2.5, - "7636": 3.0, - "77464": 3.5, - "78464": 4.0, - "7936": 4.5, - "91055": 5.0, - "91155": 5.5, - "91455": 7.0, - "91664": 8.0, - "91955": 9.5, - "92055": 10.0, - "92555": 12.5 -} -SMS_XML.PARAMS = ["send_number", "sms_text", "sms_from", "sms_id", "sign"] \ No newline at end of file diff --git a/simpay/sms_xml/models.py b/simpay/sms_xml/models.py new file mode 100644 index 0000000..01905be --- /dev/null +++ b/simpay/sms_xml/models.py @@ -0,0 +1,21 @@ +CODES = { + "7055": 0.25, + "7136": 0.5, + "7255": 1.0, + "7355": 1.5, + "7455": 2.0, + "7555": 2.5, + "7636": 3.0, + "77464": 3.5, + "78464": 4.0, + "7936": 4.5, + "91055": 5.0, + "91155": 5.5, + "91455": 7.0, + "91664": 8.0, + "91955": 9.5, + "92055": 10.0, + "92555": 12.5 +} + +PARAMS = ["send_number", "sms_text", "sms_from", "sms_id", "sign"] diff --git a/tests/sms_new.py b/tests/sms_new.py new file mode 100644 index 0000000..2ec8ca1 --- /dev/null +++ b/tests/sms_new.py @@ -0,0 +1,28 @@ +from simpay import Client + +apiKey = "" +apiPassword = "" +client = Client(apiKey, apiPassword) +hash = 'MhHCeKSDWNc32LXR' +# print(client.SMS.verify_code(3574, '384AA4').data) +# print(client.SMS.get_service('7886f5b3')) +# print(client.SMS.get_services()) +# print(client.SMS.get_transaction(3574, 2276879)) +# print(client.SMS.get_transaction('ccbc4b7a', 2276879)) +# print(client.SMS.get_services()) +# print(client.DirectBilling.generate_transaction('c7d8b925', hash, 1)) + +print(client.DirectBilling.generate_transaction('c7d8b925', hash, 10)) +print(client.DirectBilling.verify_transaction({ + "id": "82493ac0-253a-4c4b-bc1e-0578e0352fed", + "service_id": "c7d8b925", + "status": "transaction_db_confirmed", + "values": { + "net": 1, + "gross": 1.23, + "partner": 0.67 + }, + "number_from": 48440200126, + "provider": 1, + "signature": "f554829dd3282fd9239dd28b6e253e900edbac89f5303115286d94d1f8ebe242" +}, hash)) \ No newline at end of file From d935066cd313a1fa1de10aab3876be40731222e6 Mon Sep 17 00:00:00 2001 From: mwoznowski Date: Wed, 17 May 2023 19:15:49 +0200 Subject: [PATCH 2/7] Refactor of SMS XML method --- simpay/simpay.py | 2 ++ simpay/sms_xml/client.py | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/simpay/simpay.py b/simpay/simpay.py index d983213..88a1f3c 100644 --- a/simpay/simpay.py +++ b/simpay/simpay.py @@ -1,6 +1,7 @@ from .__version__ import VERSION from simpay.baseModel import RequestMethod, Response from simpay.sms.client import SMSClient +from simpay.sms_xml.client import SMSXMLClient from simpay.directbilling.client import DirectBillingClient import requests @@ -42,6 +43,7 @@ def __init__( self._http_client.headers['X-SIM-PASSWORD'] = self.api_password self.SMS: SMSClient = SMSClient(self) + self.SMS_XML: SMSXMLClient = SMSXMLClient(self) self.DirectBilling: DirectBillingClient = DirectBillingClient(self) def request(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> Response: diff --git a/simpay/sms_xml/client.py b/simpay/sms_xml/client.py index c1d6767..4a87805 100644 --- a/simpay/sms_xml/client.py +++ b/simpay/sms_xml/client.py @@ -1,13 +1,16 @@ +from typing import TYPE_CHECKING import hashlib import random import string import unicodedata from simpay.sms_xml.models import PARAMS +if TYPE_CHECKING: + from simpay import Client -class SMS_XML: - def __init__(self, api_key): - self.api_key = api_key +class SMSXMLClient: + def __init__(self, client: 'Client') -> None: + self._client = client def check_parameters(self, request): for param in PARAMS: @@ -38,6 +41,6 @@ def sign(self, request): str(request.get("sms_from")) + str(request.get("send_number")) + str(request.get("send_time")) + - str(self.api_key), + str(self._client.api_key), encoding="utf8") ).hexdigest() From da0d9c225f665a64ee3a5aedeb9ac00cc6461636 Mon Sep 17 00:00:00 2001 From: mwoznowski Date: Mon, 22 May 2023 22:48:17 +0200 Subject: [PATCH 3/7] Add sphinx docs and part of comments --- requirements.txt | 3 --- setup.py | 9 +++++---- simpay/simpay.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index aaabdc3..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests~=2.26.0 -setuptools~=57.0.0 -pydantic~=1.10.7 \ No newline at end of file diff --git a/setup.py b/setup.py index a874c8f..f1c7531 100644 --- a/setup.py +++ b/setup.py @@ -8,9 +8,10 @@ description='Python wrapper for Simpay API', author='Rafał Więcek ', url='https://github.com/SimPaypl/SimPay-API-Python', - python_requires=">3.0", - packages=find_packages(exclude=["tests*", "examples*"]), - test_suite="tests", + python_requires='>3.0', + packages=find_packages(exclude=['tests*', 'examples*']), + test_suite='tests', include_package_data=True, - install_requires=['requests'], + install_requires=['requests>=2.26.0', 'pydantic>=1.10.7'], + extras_require={'docs': ['sphinx>=7.0.1']}, ) \ No newline at end of file diff --git a/simpay/simpay.py b/simpay/simpay.py index 88a1f3c..41cb3b7 100644 --- a/simpay/simpay.py +++ b/simpay/simpay.py @@ -7,6 +7,8 @@ class ClientException(Exception): + """Base exception of client requests""" + def __init__(self, code=200, message=None, details=None): self.code = code self.message = message @@ -17,6 +19,8 @@ def __str__(self): class Client(object): + """Base client of SimPay""" + _version = VERSION _user_agent = 'simpay-python-api' _base_endpoint = 'https://api.simpay.pl/' @@ -27,6 +31,15 @@ def __init__( api_password: str | None, timeout: int = 5 ) -> None: + """Base client of SimPay + + :param api_key: str + API key from account details + :param api_password: str + API password from account details + :param timeout: int + Timeout of HTTP request, default its 5 seconds + """ self.api_key = api_key self.api_password = api_password self.timeout = timeout @@ -42,11 +55,35 @@ def __init__( if self.api_password is not None: self._http_client.headers['X-SIM-PASSWORD'] = self.api_password + """Instance of API interface SMS methods + + :type: :class:`SMSClient ` + """ self.SMS: SMSClient = SMSClient(self) + """Instance of API interface SMS XML methods + + :type: :class:`SMSXMLClient ` + """ self.SMS_XML: SMSXMLClient = SMSXMLClient(self) + """Instance of API interface DirectBilling methods + + :type: :class:`DirectBillingClient ` + """ self.DirectBilling: DirectBillingClient = DirectBillingClient(self) def request(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> Response: + """Base client of SimPay + + :param method: RequestMethod + HTTP request method (GET, POST, ...) + :param api_password: str + API password from account details + :param timeout: int + Timeout of HTTP request, default its 5 seconds + + :return Response body + :rtype dict + """ response = self._http_client.request( method.value, self._base_endpoint + uri, params=options, json=fields, headers=headers) if len(response.content) == 0: From 2567d78b50c6a2c0096e9b41537e889177276152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Wo=C5=BAnowski?= Date: Wed, 7 Jun 2023 02:01:58 +0200 Subject: [PATCH 4/7] Add sphinx docs and blocks of methods, models --- simpay/baseModel.py | 32 ++++++++ simpay/directbilling/models.py | 144 ++++++++++++++++++++++++++++++++- simpay/simpay.py | 35 ++++++-- simpay/sms/models.py | 44 ++++++++++ 4 files changed, 247 insertions(+), 8 deletions(-) diff --git a/simpay/baseModel.py b/simpay/baseModel.py index ccd8ee5..e6c22d2 100644 --- a/simpay/baseModel.py +++ b/simpay/baseModel.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import TypeVar, Generic from enum import Enum from pydantic import BaseModel @@ -15,11 +16,33 @@ class RequestMethod(Enum): class ResponsePaginationLinks(BaseModel): + """Pagination links + + :param next_page: str | None + Next page link + :param prev_page: str | None + Previous page link + """ next_page: str | None prev_page: str | None class ResponsePagination(BaseModel): + """Request pagination + + :param total: int + Total items in all pages + :param count: int + Count of items at current page + :param per_page: int + Limit of served items at one page + :param current_page: int + Current page + :param total_pages: int + Total pages at pagination + :param links: ResponsePaginationLinks + Pagination links (next page, previous page) + """ total: int count: int per_page: int @@ -29,6 +52,15 @@ class ResponsePagination(BaseModel): class Response(Generic[T]): + """Request response + + :param success: bool + Determinate if request has been procesed successfully + :param data: any + Response body data, data can be list of items or object of item + :param pagination: ResponsePagination | none + Pagination + """ success: bool = False data: T | None = None pagination: ResponsePagination | None diff --git a/simpay/directbilling/models.py b/simpay/directbilling/models.py index 9e07f5f..26d984a 100644 --- a/simpay/directbilling/models.py +++ b/simpay/directbilling/models.py @@ -1,3 +1,4 @@ +from __future__ import annotations from datetime import datetime from enum import Enum from pydantic import BaseModel @@ -33,6 +34,17 @@ class DCB_Provider(Enum): class DCB_Service_Providers(BaseModel): + """DCB service available providers + + :param tmobile: bool + Provider t-mobile available status, origin key from api's it's 't-mobile' + :param orange: bool + Type of service + :param play: bool + Name of service + :param plus: bool + Prefix of SMS + """ tmobile: bool orange: bool play: bool @@ -44,6 +56,17 @@ class Config: class DCB_Service_MaxValues(BaseModel): + """DCB service max values per single transaction + + :param tmobile: str + Value for t-mobile provider, origin key from api's it's 't-mobile' + :param orange: str + Value for orange provider + :param play: str + Value for orange provider + :param plus: str + Value for orange provider + """ tmobile: str orange: str play: str @@ -55,12 +78,32 @@ class Config: class DCB_Service_Commission(BaseModel): + """DCB servicem stages of comission rates + + :param commission_0: str + Commision rate from 0,01 PLN to 9,99 PLN amount of transaction + :param commission_9: str + Commision rate from 9 PLN to 24,99 PLN amount of transaction + :param commission_25: str + Commision rate from 25 PLN to max amount of transaction + """ commission_0: str commission_9: str commission_25: str class DCB_Service_Commissions(BaseModel): + """DCB service comission rates + + :param tmobile: DCB_Service_Commission + Commision rate for t-mobile provider, origin key from api's it's 't-mobile' + :param orange: DCB_Service_Commission + Commision rate for orange provider + :param play: DCB_Service_Commission + Commision rate for play provider + :param plus: DCB_Service_Commission + Commision rate for plus provider + """ tmobile: DCB_Service_Commission orange: DCB_Service_Commission play: DCB_Service_Commission @@ -72,12 +115,32 @@ class Config: class DCB_Service_Values(BaseModel): + """DCB service and transaction values + + :param net: float + Amount net + :param gross: float + Amount gross + :param partner: float | None + Partner comission amount from transaction + """ net: float gross: float partner: float | None class DCB_Service_CalculatedCommissions(BaseModel): + """DCB service calculated comissions + + :param tmobile: DCB_Service_Values | None + Values for t-mobile provider, origin key from api's it's 't-mobile' + :param orange: DCB_Service_Values | None + Values for orange provider + :param play: DCB_Service_Values | None + Values for play provider + :param plus: DCB_Service_Values | None + Values for plus provider + """ tmobile: DCB_Service_Values | None orange: DCB_Service_Values | None play: DCB_Service_Values | None @@ -89,11 +152,39 @@ class Config: class DCB_Service_API(BaseModel): + """DCB service redirection urls + + :param complete: str + Redirection url after complete transaction + :param failure: str + Redirection url after failure transaction + """ complete: str failure: str class DCB_Service(BaseModel): + """DCB service + + :param id: str + ID of DirectBilling service + :param name: str + Name of DirectBilling service + :param suffix: str + Suffix + :param status: DCB_Service_Status + Status + :param api: DCB_Service_API + API endpoints + :param providers: DCB_Service_Providers + Available providers + :param commissions: DCB_Service_Commissions + Commisions + :param maxValues: DCB_Service_MaxValues + Max values per transaction + :param created_at: datetime + Creation time of service + """ id: str name: str suffix: str @@ -106,21 +197,65 @@ class DCB_Service(BaseModel): class DCB_Transaction_Notify(BaseModel): + """DCB transaction notification states + + :param is_send: str + Status whether notification has been sended + :param last_send_at: str + Date of last notification attempt + :param count: str + Count of notification attemps + """ is_send: bool last_send_at: datetime count: int class DCB_Transaction_Returns(BaseModel): + """DCB transaction redirection urls + + :param complete: str + URL where customer will be redirected after successful transaction + :param failure: str + URL where customer will be redirected after failure transaction + """ complete: str failure: str class DCB_Transaction(BaseModel): + """DCB transaction + + :param id: str + ID of DirectBilling of transaction + :param status: DCB_Transaction_Status + Status + :param phoneNumber: str | None + Phone number which transaction has been processed, original key from api it's number_from + :param control: str | None + Own custom property + :param value: float | None + Amount of transaction + :param value_netto: float | None + Amount of transaction (presented as netto value) + :param operator: str | None + Operator of transaction + :param returns: DCB_Transaction_Returns | None + Returns endpoints after successful or failure transaction + :param notify: DCB_Transaction_Notify | None + Nofication statuses + :param provider: int | None + ID of transaction provider + :param signature: str | None + Signature of transaction based on sha256 + :param created_at: datetime | None + Creation date of transaction + :param updated_at: datetime | None + Update date of transaction + """ id: str status: DCB_Transaction_Status phoneNumber: str | None - control: str | None value: float | None value_netto: float | None values: DCB_Service_Values | None @@ -139,5 +274,12 @@ class Config: class DCB_GeneratedTransaction(BaseModel): + """DCB generating transaction callback + + :param transactionId: str + Transaction ID + :param redirectUrl: str + Redirection url for transaction + """ transactionId: str redirectUrl: str diff --git a/simpay/simpay.py b/simpay/simpay.py index 41cb3b7..2e91282 100644 --- a/simpay/simpay.py +++ b/simpay/simpay.py @@ -1,3 +1,4 @@ +from __future__ import annotations from .__version__ import VERSION from simpay.baseModel import RequestMethod, Response from simpay.sms.client import SMSClient @@ -71,15 +72,19 @@ def __init__( """ self.DirectBilling: DirectBillingClient = DirectBillingClient(self) - def request(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> Response: - """Base client of SimPay + def request(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> dict: + """HTTP request builder :param method: RequestMethod HTTP request method (GET, POST, ...) - :param api_password: str - API password from account details - :param timeout: int - Timeout of HTTP request, default its 5 seconds + :param uri: str + API endpoint uri without base url + :param fields: dict[str, any] + HTTP POST fields (converted into JSON body content type) + :param headers: dict[str, any] + HTTP request headers + :param options: dict[str, any] + HTTP request builder options like pagination page, per page items :return Response body :rtype dict @@ -96,7 +101,23 @@ def request(self, method: RequestMethod, uri: str, fields: dict[str, any] | None raise ClientException(response.status_code) return response.json() - def requestAllPages(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> object: + def requestAllPages(self, method: RequestMethod, uri: str, fields: dict[str, any] | None = None, headers: dict[str, any] | None = None, options: dict[str, any] | None = None) -> list: + """HTTP request builder with auto fetch pagination all pages + + :param method: RequestMethod + HTTP request method (GET, POST, ...) + :param uri: str + API endpoint uri without base url + :param fields: dict[str, any] + HTTP POST fields (converted into JSON body content type) + :param headers: dict[str, any] + HTTP request headers + :param options: dict[str, any] + HTTP request builder options like pagination page, per page items + + :return Response body items + :rtype list + """ response = self.request(method, uri, fields, headers, options) responseData = [] if response['success']: diff --git a/simpay/sms/models.py b/simpay/sms/models.py index 2c523df..0d24e9e 100644 --- a/simpay/sms/models.py +++ b/simpay/sms/models.py @@ -1,3 +1,4 @@ +from __future__ import annotations from datetime import datetime from enum import Enum from pydantic import BaseModel @@ -21,6 +22,25 @@ class SMS_Service_Type(Enum): class SMS_Service(BaseModel): + """SMS service model + + :param id: str + ID of service, short UUID + :param type: SMS_Service_Type + Type of service + :param name: str + Name of service + :param prefix: str + Prefix of SMS + :param description: str | None + Description of service + :param adult: bool + Service it's for adult content age, default it's false + :param numbers: list[int] | none + List of available numbers for this service + :param created_at: datetime + Created at + """ id: str type: SMS_Service_Type status: SMS_Service_Status @@ -34,6 +54,19 @@ class SMS_Service(BaseModel): class SMS_Transaction(BaseModel): + """SMS transaction model + + :param id: str + ID of SMS transaction, short UUID + :param from_number: int + Origin number of sender, origin from api it's 'from' + :param code: str + SMS code + :param used: bool + Determinate if code has been used + :param send_at: datetime + Sended at + """ id: str from_number: int code: str @@ -46,6 +79,17 @@ class Config: class SMS_Number(BaseModel): + """SMS number model + + :param number: int + Number of SMS + :param value: float + Net value + :param value_gross: float + Gross value + :param adult: bool + Determinate if number it's for adult content age + """ number: int value: float value_gross: float From 50e605410ffa19978c93f21bc07d066f9f16583c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wi=C4=99cek?= Date: Wed, 7 Jun 2023 02:34:14 +0200 Subject: [PATCH 5/7] Update setup.py with long description and author email The commit updates the setup.py file to include a long description of the package from README.md and adds an author email. --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f1c7531..b3dff51 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,10 @@ name='simpay-api', version='3.0', description='Python wrapper for Simpay API', - author='Rafał Więcek ', + long_description=open('README.md').read(), + long_description_content_type="text/markdown", + author='Rafał Więcek', + author_email="kontakt@simpay.pl", url='https://github.com/SimPaypl/SimPay-API-Python', python_requires='>3.0', packages=find_packages(exclude=['tests*', 'examples*']), From 5c526d5c5f5879604cf1dbae9d850e329d08c15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wi=C4=99cek?= Date: Wed, 7 Jun 2023 02:37:26 +0200 Subject: [PATCH 6/7] Fix encoding issue in setup.py for Simpay API wrapper The commit fixes an issue where the README.md file was not being read correctly due to encoding. The change adds the 'utf-8' encoding parameter to the open() function call. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b3dff51..fd7df75 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ name='simpay-api', version='3.0', description='Python wrapper for Simpay API', - long_description=open('README.md').read(), + long_description=open('README.md', encoding='utf-8').read(), long_description_content_type="text/markdown", author='Rafał Więcek', author_email="kontakt@simpay.pl", From d655e4681223bf49fd350ad2c73aa4672de520c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wi=C4=99cek?= Date: Wed, 7 Jun 2023 02:42:15 +0200 Subject: [PATCH 7/7] Update Simpay API version to 3.0.1 The Simpay API version has been updated from 3.0 to 3.0.1 in the setup.py file, which is a Python wrapper for the Simpay API. The long description of the package is now read from README.md and its content type is set as text/markdown. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fd7df75..1ee13b4 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='simpay-api', - version='3.0', + version='3.0.1', description='Python wrapper for Simpay API', long_description=open('README.md', encoding='utf-8').read(), long_description_content_type="text/markdown",