dime-python-sdk 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dime_payments/__init__.py +29 -0
- dime_payments/client.py +30 -0
- dime_payments/config.py +32 -0
- dime_payments/data_objects/__init__.py +31 -0
- dime_payments/data_objects/address.py +30 -0
- dime_payments/data_objects/customer.py +37 -0
- dime_payments/data_objects/deposit.py +31 -0
- dime_payments/data_objects/deposit_group.py +25 -0
- dime_payments/data_objects/deposit_with_transactions.py +37 -0
- dime_payments/data_objects/form_link.py +13 -0
- dime_payments/data_objects/merchant.py +55 -0
- dime_payments/data_objects/message_result.py +13 -0
- dime_payments/data_objects/payment_method.py +63 -0
- dime_payments/data_objects/recurring_payment.py +53 -0
- dime_payments/data_objects/recurring_payment_method.py +17 -0
- dime_payments/data_objects/tokenize_result.py +13 -0
- dime_payments/data_objects/transaction.py +57 -0
- dime_payments/data_objects/transaction_address.py +27 -0
- dime_payments/exceptions/__init__.py +21 -0
- dime_payments/exceptions/api_exception.py +5 -0
- dime_payments/exceptions/authentication_exception.py +5 -0
- dime_payments/exceptions/connection_exception.py +5 -0
- dime_payments/exceptions/dime_exception.py +19 -0
- dime_payments/exceptions/not_found_exception.py +5 -0
- dime_payments/exceptions/permission_denied_exception.py +5 -0
- dime_payments/exceptions/rate_limit_exception.py +18 -0
- dime_payments/exceptions/server_exception.py +5 -0
- dime_payments/exceptions/validation_exception.py +24 -0
- dime_payments/http/__init__.py +0 -0
- dime_payments/http/error_handler.py +76 -0
- dime_payments/http/transport.py +80 -0
- dime_payments/pagination/__init__.py +3 -0
- dime_payments/pagination/cursor_page.py +72 -0
- dime_payments/resources/__init__.py +0 -0
- dime_payments/resources/abstract_resource.py +49 -0
- dime_payments/resources/addresses.py +37 -0
- dime_payments/resources/customers.py +32 -0
- dime_payments/resources/deposits.py +31 -0
- dime_payments/resources/merchants.py +31 -0
- dime_payments/resources/payment_methods.py +37 -0
- dime_payments/resources/recurring_payments.py +53 -0
- dime_payments/resources/transactions.py +53 -0
- dime_payments/support/__init__.py +0 -0
- dime_payments/support/arr.py +60 -0
- dime_python_sdk-1.0.0.dist-info/METADATA +288 -0
- dime_python_sdk-1.0.0.dist-info/RECORD +48 -0
- dime_python_sdk-1.0.0.dist-info/WHEEL +4 -0
- dime_python_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .dime_exception import DimeException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RateLimitException(DimeException):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
message: str,
|
|
10
|
+
status_code: int | None = None,
|
|
11
|
+
response_body: dict[str, Any] | None = None,
|
|
12
|
+
retry_after: float | None = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
super().__init__(message, status_code, response_body)
|
|
15
|
+
self._retry_after = retry_after
|
|
16
|
+
|
|
17
|
+
def get_retry_after(self) -> float | None:
|
|
18
|
+
return self._retry_after
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .dime_exception import DimeException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ValidationException(DimeException):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
message: str,
|
|
10
|
+
status_code: int | None = None,
|
|
11
|
+
response_body: dict[str, Any] | None = None,
|
|
12
|
+
errors: dict[str, list[str]] | None = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
super().__init__(message, status_code, response_body)
|
|
15
|
+
self.errors: dict[str, list[str]] = errors or {}
|
|
16
|
+
|
|
17
|
+
def get_errors(self) -> dict[str, list[str]]:
|
|
18
|
+
return self.errors
|
|
19
|
+
|
|
20
|
+
def first_error(self) -> str | None:
|
|
21
|
+
for messages in self.errors.values():
|
|
22
|
+
if messages:
|
|
23
|
+
return messages[0]
|
|
24
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from ..exceptions import (
|
|
4
|
+
ApiException,
|
|
5
|
+
AuthenticationException,
|
|
6
|
+
NotFoundException,
|
|
7
|
+
PermissionDeniedException,
|
|
8
|
+
RateLimitException,
|
|
9
|
+
ServerException,
|
|
10
|
+
ValidationException,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ErrorHandler:
|
|
15
|
+
@staticmethod
|
|
16
|
+
def check(response: Any) -> None:
|
|
17
|
+
status: int = response.status_code
|
|
18
|
+
if 200 <= status < 300:
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
body: dict[str, Any] = response.json()
|
|
23
|
+
if not isinstance(body, dict):
|
|
24
|
+
body = {}
|
|
25
|
+
except Exception:
|
|
26
|
+
body = {}
|
|
27
|
+
|
|
28
|
+
if status in (400, 422):
|
|
29
|
+
errors = body.get('errors')
|
|
30
|
+
if isinstance(errors, dict):
|
|
31
|
+
raise ValidationException('Validation failed', status_code=status, response_body=body, errors=errors)
|
|
32
|
+
message = body.get('message')
|
|
33
|
+
if isinstance(message, dict):
|
|
34
|
+
raise ValidationException('Validation failed', status_code=status, response_body=body, errors=message)
|
|
35
|
+
msg = message if isinstance(message, str) else f'Validation failed ({status})'
|
|
36
|
+
raise ValidationException(msg, status_code=status, response_body=body)
|
|
37
|
+
|
|
38
|
+
if status == 401:
|
|
39
|
+
msg = body.get('message', 'Unauthenticated')
|
|
40
|
+
raise AuthenticationException(str(msg), status_code=status, response_body=body)
|
|
41
|
+
|
|
42
|
+
if status == 403:
|
|
43
|
+
data_section = body.get('data') or {}
|
|
44
|
+
msg = (
|
|
45
|
+
body.get('message')
|
|
46
|
+
or (data_section.get('message') if isinstance(data_section, dict) else None)
|
|
47
|
+
or 'Forbidden'
|
|
48
|
+
)
|
|
49
|
+
raise PermissionDeniedException(str(msg), status_code=status, response_body=body)
|
|
50
|
+
|
|
51
|
+
if status == 404:
|
|
52
|
+
data_section = body.get('data') or {}
|
|
53
|
+
msg = (
|
|
54
|
+
body.get('message')
|
|
55
|
+
or (data_section.get('message') if isinstance(data_section, dict) else None)
|
|
56
|
+
or 'Not found'
|
|
57
|
+
)
|
|
58
|
+
raise NotFoundException(str(msg), status_code=status, response_body=body)
|
|
59
|
+
|
|
60
|
+
if status == 429:
|
|
61
|
+
retry_after_hdr = response.headers.get('Retry-After')
|
|
62
|
+
retry_after: float | None = None
|
|
63
|
+
if retry_after_hdr:
|
|
64
|
+
try:
|
|
65
|
+
retry_after = float(retry_after_hdr)
|
|
66
|
+
except (ValueError, TypeError):
|
|
67
|
+
pass
|
|
68
|
+
msg = body.get('message', 'Too many requests')
|
|
69
|
+
raise RateLimitException(str(msg), status_code=status, response_body=body, retry_after=retry_after)
|
|
70
|
+
|
|
71
|
+
if status >= 500:
|
|
72
|
+
msg = body.get('message', 'Server error')
|
|
73
|
+
raise ServerException(str(msg), status_code=status, response_body=body)
|
|
74
|
+
|
|
75
|
+
msg = body.get('message', f'HTTP {status}')
|
|
76
|
+
raise ApiException(str(msg), status_code=status, response_body=body)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from ..exceptions.connection_exception import ConnectionException
|
|
9
|
+
from .error_handler import ErrorHandler
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..config import Config
|
|
13
|
+
|
|
14
|
+
SDK_VERSION = '1.0.0'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Transport:
|
|
18
|
+
def __init__(self, config: Config) -> None:
|
|
19
|
+
self._config = config
|
|
20
|
+
if config._session is not None:
|
|
21
|
+
self._session = config._session
|
|
22
|
+
else:
|
|
23
|
+
self._session = requests.Session()
|
|
24
|
+
self._session.headers.update({
|
|
25
|
+
'Authorization': f'Bearer {config.token}',
|
|
26
|
+
'Accept': 'application/json',
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'X-Dime-Sdk': f'dime-python-sdk/{SDK_VERSION}',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
def request(
|
|
32
|
+
self,
|
|
33
|
+
method: str,
|
|
34
|
+
path: str,
|
|
35
|
+
body: dict[str, Any] | None = None,
|
|
36
|
+
query: dict[str, Any] | None = None,
|
|
37
|
+
) -> dict[str, Any]:
|
|
38
|
+
url = self._config.base_uri() + path.lstrip('/')
|
|
39
|
+
params = {k: v for k, v in (query or {}).items() if v is not None} or None
|
|
40
|
+
|
|
41
|
+
attempt = 0
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
response = self._session.request(
|
|
46
|
+
method.upper(),
|
|
47
|
+
url,
|
|
48
|
+
json=body or None,
|
|
49
|
+
params=params,
|
|
50
|
+
timeout=self._config.timeout,
|
|
51
|
+
)
|
|
52
|
+
except requests.exceptions.RequestException as exc:
|
|
53
|
+
if attempt < self._config.max_retries:
|
|
54
|
+
self._do_sleep(self._config.retry_base_delay * (2 ** attempt))
|
|
55
|
+
attempt += 1
|
|
56
|
+
continue
|
|
57
|
+
raise ConnectionException(f'Could not reach the Dime API: {exc}') from exc
|
|
58
|
+
|
|
59
|
+
if (response.status_code == 429 or response.status_code >= 500) and attempt < self._config.max_retries:
|
|
60
|
+
self._do_sleep(self._retry_delay(attempt, response))
|
|
61
|
+
attempt += 1
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
ErrorHandler.check(response)
|
|
65
|
+
return response.json()
|
|
66
|
+
|
|
67
|
+
def _retry_delay(self, attempt: int, response: Any) -> float:
|
|
68
|
+
retry_after = response.headers.get('Retry-After')
|
|
69
|
+
if retry_after:
|
|
70
|
+
try:
|
|
71
|
+
return float(retry_after)
|
|
72
|
+
except (ValueError, TypeError):
|
|
73
|
+
pass
|
|
74
|
+
return min(self._config.retry_base_delay * (2 ** attempt), 30.0)
|
|
75
|
+
|
|
76
|
+
def _do_sleep(self, seconds: float) -> None:
|
|
77
|
+
if self._config._sleep is not None:
|
|
78
|
+
self._config._sleep(seconds)
|
|
79
|
+
else:
|
|
80
|
+
time.sleep(seconds)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Generic, Iterator, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar('T')
|
|
6
|
+
PageFetcher = Callable[[str | None], 'CursorPage[T]']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CursorPage(Generic[T]):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
data: list[T],
|
|
13
|
+
next_cursor: str | None = None,
|
|
14
|
+
prev_cursor: str | None = None,
|
|
15
|
+
per_page: int | None = None,
|
|
16
|
+
path: str | None = None,
|
|
17
|
+
fetcher: PageFetcher | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
self.data = data
|
|
20
|
+
self.next_cursor = next_cursor
|
|
21
|
+
self.prev_cursor = prev_cursor
|
|
22
|
+
self.per_page = per_page
|
|
23
|
+
self.path = path
|
|
24
|
+
self._fetcher = fetcher
|
|
25
|
+
|
|
26
|
+
def has_more(self) -> bool:
|
|
27
|
+
return self.next_cursor is not None and self._fetcher is not None
|
|
28
|
+
|
|
29
|
+
def next(self) -> CursorPage[T] | None:
|
|
30
|
+
if not self.has_more():
|
|
31
|
+
return None
|
|
32
|
+
assert self._fetcher is not None
|
|
33
|
+
return self._fetcher(self.next_cursor)
|
|
34
|
+
|
|
35
|
+
def auto_paging(self) -> Iterator[T]:
|
|
36
|
+
page: CursorPage[T] | None = self
|
|
37
|
+
while page is not None:
|
|
38
|
+
# Yield items individually (not yield from) so indices stay sequential.
|
|
39
|
+
for item in page.data:
|
|
40
|
+
yield item
|
|
41
|
+
page = page.next()
|
|
42
|
+
|
|
43
|
+
def __iter__(self) -> Iterator[T]:
|
|
44
|
+
return iter(self.data)
|
|
45
|
+
|
|
46
|
+
def __len__(self) -> int:
|
|
47
|
+
return len(self.data)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_response(
|
|
51
|
+
cls,
|
|
52
|
+
items: list[T],
|
|
53
|
+
response: dict[str, Any],
|
|
54
|
+
fetcher: PageFetcher,
|
|
55
|
+
) -> CursorPage[T]:
|
|
56
|
+
meta = response.get('meta') or {}
|
|
57
|
+
if not isinstance(meta, dict):
|
|
58
|
+
meta = {}
|
|
59
|
+
|
|
60
|
+
next_cursor = meta.get('next_cursor')
|
|
61
|
+
prev_cursor = meta.get('prev_cursor')
|
|
62
|
+
per_page = meta.get('per_page')
|
|
63
|
+
path = meta.get('path')
|
|
64
|
+
|
|
65
|
+
return cls(
|
|
66
|
+
data=items,
|
|
67
|
+
next_cursor=next_cursor if isinstance(next_cursor, str) and next_cursor else None,
|
|
68
|
+
prev_cursor=prev_cursor if isinstance(prev_cursor, str) and prev_cursor else None,
|
|
69
|
+
per_page=int(per_page) if isinstance(per_page, (int, float, str)) and str(per_page).isdigit() else None,
|
|
70
|
+
path=path if isinstance(path, str) else None,
|
|
71
|
+
fetcher=fetcher,
|
|
72
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
from ..http.transport import Transport
|
|
6
|
+
from ..pagination.cursor_page import CursorPage
|
|
7
|
+
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AbstractResource:
|
|
12
|
+
def __init__(self, transport: Transport) -> None:
|
|
13
|
+
self._transport = transport
|
|
14
|
+
|
|
15
|
+
def _envelope(
|
|
16
|
+
self,
|
|
17
|
+
data: dict[str, Any] | None = None,
|
|
18
|
+
filters: dict[str, Any] | None = None,
|
|
19
|
+
) -> dict[str, Any]:
|
|
20
|
+
body: dict[str, Any] = {}
|
|
21
|
+
if data:
|
|
22
|
+
pruned = {k: v for k, v in data.items() if v is not None}
|
|
23
|
+
if pruned:
|
|
24
|
+
body['data'] = pruned
|
|
25
|
+
if filters:
|
|
26
|
+
pruned_f = {k: v for k, v in filters.items() if v is not None}
|
|
27
|
+
if pruned_f:
|
|
28
|
+
body['filters'] = pruned_f
|
|
29
|
+
return body
|
|
30
|
+
|
|
31
|
+
def _paginate(
|
|
32
|
+
self,
|
|
33
|
+
method: str,
|
|
34
|
+
path: str,
|
|
35
|
+
body: dict[str, Any],
|
|
36
|
+
mapper: Callable[[dict[str, Any]], T],
|
|
37
|
+
query: dict[str, Any] | None = None,
|
|
38
|
+
) -> CursorPage[T]:
|
|
39
|
+
def fetcher(cursor: str | None) -> CursorPage[T]:
|
|
40
|
+
return self._paginate(method, path, body, mapper, {'cursor': cursor})
|
|
41
|
+
|
|
42
|
+
response = self._transport.request(method, path, body, query)
|
|
43
|
+
|
|
44
|
+
raw_data = response.get('data', [])
|
|
45
|
+
if isinstance(raw_data, dict):
|
|
46
|
+
raw_data = list(raw_data.values())
|
|
47
|
+
|
|
48
|
+
items = [mapper(item) for item in raw_data if isinstance(item, dict)]
|
|
49
|
+
return CursorPage.from_response(items, response, fetcher)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.address import Address
|
|
6
|
+
from ..data_objects.message_result import MessageResult
|
|
7
|
+
from ..pagination.cursor_page import CursorPage
|
|
8
|
+
from .abstract_resource import AbstractResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Addresses(AbstractResource):
|
|
12
|
+
def list(self, sid: str, uuid: str) -> CursorPage[Address]:
|
|
13
|
+
body = self._envelope({'sid': sid, 'uuid': uuid})
|
|
14
|
+
return self._paginate('GET', 'address/list', body, Address.from_dict)
|
|
15
|
+
|
|
16
|
+
def show(self, sid: str, uuid: str, address_id: int | str) -> Address:
|
|
17
|
+
raw = self._transport.request(
|
|
18
|
+
'GET',
|
|
19
|
+
'address/show',
|
|
20
|
+
self._envelope({'sid': sid, 'uuid': uuid, 'address_id': address_id}),
|
|
21
|
+
)
|
|
22
|
+
return Address.from_dict(raw.get('data') or {})
|
|
23
|
+
|
|
24
|
+
def create(self, sid: str, uuid: str, attributes: dict[str, Any]) -> Address:
|
|
25
|
+
body = self._envelope({'sid': sid, 'uuid': uuid} | attributes)
|
|
26
|
+
raw = self._transport.request('POST', 'address/create', body)
|
|
27
|
+
return Address.from_dict(raw.get('data') or {})
|
|
28
|
+
|
|
29
|
+
def update(self, sid: str, uuid: str, address_id: int | str, attributes: dict[str, Any]) -> Address:
|
|
30
|
+
body = self._envelope({'sid': sid, 'uuid': uuid, 'address_id': address_id} | attributes)
|
|
31
|
+
raw = self._transport.request('PATCH', 'address/update', body)
|
|
32
|
+
return Address.from_dict(raw.get('data') or {})
|
|
33
|
+
|
|
34
|
+
def delete(self, sid: str, uuid: str, address_id: int | str) -> MessageResult:
|
|
35
|
+
body = self._envelope({'sid': sid, 'uuid': uuid, 'address_id': address_id})
|
|
36
|
+
raw = self._transport.request('POST', 'address/delete', body)
|
|
37
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.customer import Customer
|
|
6
|
+
from ..data_objects.message_result import MessageResult
|
|
7
|
+
from ..pagination.cursor_page import CursorPage
|
|
8
|
+
from .abstract_resource import AbstractResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Customers(AbstractResource):
|
|
12
|
+
def list(self, sid: str, filters: dict[str, Any] | None = None) -> CursorPage[Customer]:
|
|
13
|
+
body = self._envelope({'sid': sid}, filters or {})
|
|
14
|
+
return self._paginate('GET', 'customer/list', body, Customer.from_dict)
|
|
15
|
+
|
|
16
|
+
def show(self, sid: str, filters: dict[str, Any]) -> Customer:
|
|
17
|
+
raw = self._transport.request('GET', 'customer/show', self._envelope({'sid': sid}, filters))
|
|
18
|
+
return Customer.from_dict(raw.get('data') or {})
|
|
19
|
+
|
|
20
|
+
def create(self, sid: str, attributes: dict[str, Any]) -> Customer:
|
|
21
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
22
|
+
raw = self._transport.request('POST', 'customer/create', body)
|
|
23
|
+
return Customer.from_dict(raw.get('data') or {})
|
|
24
|
+
|
|
25
|
+
def update(self, sid: str, filters: dict[str, Any], attributes: dict[str, Any]) -> Customer:
|
|
26
|
+
body = self._envelope({'sid': sid} | attributes, filters)
|
|
27
|
+
raw = self._transport.request('PATCH', 'customer/update', body)
|
|
28
|
+
return Customer.from_dict(raw.get('data') or {})
|
|
29
|
+
|
|
30
|
+
def delete(self, sid: str, filters: dict[str, Any]) -> MessageResult:
|
|
31
|
+
raw = self._transport.request('POST', 'customer/delete', self._envelope({'sid': sid}, filters))
|
|
32
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.deposit import Deposit
|
|
6
|
+
from ..data_objects.deposit_group import DepositGroup
|
|
7
|
+
from ..data_objects.deposit_with_transactions import DepositWithTransactions
|
|
8
|
+
from ..pagination.cursor_page import CursorPage
|
|
9
|
+
from .abstract_resource import AbstractResource
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Deposits(AbstractResource):
|
|
13
|
+
def list(self, sid: str, filters: dict[str, Any] | None = None) -> CursorPage[Deposit]:
|
|
14
|
+
body = self._envelope({'sid': sid}, filters or {})
|
|
15
|
+
return self._paginate('GET', 'deposit/list', body, Deposit.from_dict)
|
|
16
|
+
|
|
17
|
+
def list_with_transactions(self, sid: str, filters: dict[str, Any]) -> DepositGroup:
|
|
18
|
+
raw = self._transport.request(
|
|
19
|
+
'GET',
|
|
20
|
+
'deposit/list-with-trans',
|
|
21
|
+
self._envelope({'sid': sid}, filters),
|
|
22
|
+
)
|
|
23
|
+
return DepositGroup.from_dict(raw.get('data') or {})
|
|
24
|
+
|
|
25
|
+
def show(self, sid: str, identifier: dict[str, Any]) -> DepositWithTransactions:
|
|
26
|
+
raw = self._transport.request(
|
|
27
|
+
'GET',
|
|
28
|
+
'deposit/show',
|
|
29
|
+
self._envelope({'sid': sid} | identifier),
|
|
30
|
+
)
|
|
31
|
+
return DepositWithTransactions.from_dict(raw.get('data') or {})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.form_link import FormLink
|
|
6
|
+
from ..data_objects.merchant import Merchant
|
|
7
|
+
from ..pagination.cursor_page import CursorPage
|
|
8
|
+
from .abstract_resource import AbstractResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Merchants(AbstractResource):
|
|
12
|
+
def list(self, filters: dict[str, Any] | None = None) -> CursorPage[Merchant]:
|
|
13
|
+
body = self._envelope({}, filters or {})
|
|
14
|
+
return self._paginate('GET', 'merchant/list', body, Merchant.from_dict)
|
|
15
|
+
|
|
16
|
+
def show(self, sid: str) -> Merchant:
|
|
17
|
+
raw = self._transport.request('GET', 'merchant/show', self._envelope({'sid': sid}))
|
|
18
|
+
return Merchant.from_dict(raw.get('data') or {})
|
|
19
|
+
|
|
20
|
+
def create(self, attributes: dict[str, Any]) -> Merchant:
|
|
21
|
+
raw = self._transport.request('POST', 'merchant/create', self._envelope(attributes))
|
|
22
|
+
return Merchant.from_dict(raw.get('data') or {})
|
|
23
|
+
|
|
24
|
+
def update(self, sid: str, attributes: dict[str, Any]) -> Merchant:
|
|
25
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
26
|
+
raw = self._transport.request('PATCH', 'merchant/update', body)
|
|
27
|
+
return Merchant.from_dict(raw.get('data') or {})
|
|
28
|
+
|
|
29
|
+
def get_form_link(self, sid: str) -> FormLink:
|
|
30
|
+
raw = self._transport.request('GET', 'merchant/get-form-link', self._envelope({'sid': sid}))
|
|
31
|
+
return FormLink.from_dict(raw.get('data') or {})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.message_result import MessageResult
|
|
6
|
+
from ..data_objects.payment_method import PaymentMethod
|
|
7
|
+
from ..pagination.cursor_page import CursorPage
|
|
8
|
+
from .abstract_resource import AbstractResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PaymentMethods(AbstractResource):
|
|
12
|
+
def list(self, sid: str, filters: dict[str, Any]) -> CursorPage[PaymentMethod]:
|
|
13
|
+
body = self._envelope({'sid': sid}, filters)
|
|
14
|
+
return self._paginate('GET', 'payment-method/list', body, PaymentMethod.from_dict)
|
|
15
|
+
|
|
16
|
+
def show(self, sid: str, payment_method_id: int | str, filters: dict[str, Any]) -> PaymentMethod:
|
|
17
|
+
raw = self._transport.request(
|
|
18
|
+
'GET',
|
|
19
|
+
'payment-method/show',
|
|
20
|
+
self._envelope({'sid': sid, 'payment_method_id': payment_method_id}, filters),
|
|
21
|
+
)
|
|
22
|
+
return PaymentMethod.from_dict(raw.get('data') or {})
|
|
23
|
+
|
|
24
|
+
def create(self, sid: str, attributes: dict[str, Any]) -> PaymentMethod:
|
|
25
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
26
|
+
raw = self._transport.request('POST', 'payment-method/create', body)
|
|
27
|
+
return PaymentMethod.from_dict(raw.get('data') or {})
|
|
28
|
+
|
|
29
|
+
def update(self, sid: str, attributes: dict[str, Any]) -> PaymentMethod:
|
|
30
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
31
|
+
raw = self._transport.request('PATCH', 'payment-method/update', body)
|
|
32
|
+
return PaymentMethod.from_dict(raw.get('data') or {})
|
|
33
|
+
|
|
34
|
+
def delete(self, sid: str, payment_method_id: int | str, uuid: str) -> MessageResult:
|
|
35
|
+
body = self._envelope({'sid': sid, 'payment_method_id': payment_method_id, 'uuid': uuid})
|
|
36
|
+
raw = self._transport.request('POST', 'payment-method/delete', body)
|
|
37
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.message_result import MessageResult
|
|
6
|
+
from ..data_objects.recurring_payment import RecurringPayment
|
|
7
|
+
from ..pagination.cursor_page import CursorPage
|
|
8
|
+
from .abstract_resource import AbstractResource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RecurringPayments(AbstractResource):
|
|
12
|
+
def list(self, sid: str, filters: dict[str, Any] | None = None) -> CursorPage[RecurringPayment]:
|
|
13
|
+
body = self._envelope({'sid': sid}, filters or {})
|
|
14
|
+
return self._paginate('GET', 'recurring-payment/list', body, RecurringPayment.from_dict)
|
|
15
|
+
|
|
16
|
+
def show(self, sid: str, recurring_payment_id: int | str) -> RecurringPayment:
|
|
17
|
+
body = self._envelope({'sid': sid, 'recurring_payment_id': recurring_payment_id})
|
|
18
|
+
raw = self._transport.request('GET', 'recurring-payment/show', body)
|
|
19
|
+
return RecurringPayment.from_dict(raw.get('data') or {})
|
|
20
|
+
|
|
21
|
+
def create(self, sid: str, attributes: dict[str, Any]) -> RecurringPayment:
|
|
22
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
23
|
+
raw = self._transport.request('POST', 'recurring-payment/create', body)
|
|
24
|
+
return RecurringPayment.from_dict(raw.get('data') or {})
|
|
25
|
+
|
|
26
|
+
def edit(self, sid: str, recurring_payment_id: int | str, attributes: dict[str, Any]) -> RecurringPayment:
|
|
27
|
+
body = self._envelope({'sid': sid, 'recurring_payment_id': recurring_payment_id} | attributes)
|
|
28
|
+
raw = self._transport.request('PATCH', 'recurring-payment/edit', body)
|
|
29
|
+
return RecurringPayment.from_dict(raw.get('data') or {})
|
|
30
|
+
|
|
31
|
+
def pause(self, sid: str, recurring_payment_id: int | str, pause_until_date: str | None = None) -> RecurringPayment:
|
|
32
|
+
body = self._envelope({
|
|
33
|
+
'sid': sid,
|
|
34
|
+
'recurring_payment_id': recurring_payment_id,
|
|
35
|
+
'pause_until_date': pause_until_date,
|
|
36
|
+
})
|
|
37
|
+
raw = self._transport.request('PATCH', 'recurring-payment/pause', body)
|
|
38
|
+
return RecurringPayment.from_dict(raw.get('data') or {})
|
|
39
|
+
|
|
40
|
+
def cancel(self, sid: str, recurring_payment_id: int | str) -> RecurringPayment:
|
|
41
|
+
body = self._envelope({'sid': sid, 'recurring_payment_id': recurring_payment_id})
|
|
42
|
+
raw = self._transport.request('PATCH', 'recurring-payment/cancel', body)
|
|
43
|
+
return RecurringPayment.from_dict(raw.get('data') or {})
|
|
44
|
+
|
|
45
|
+
def activate(self, sid: str, recurring_payment_id: int | str) -> RecurringPayment:
|
|
46
|
+
body = self._envelope({'sid': sid, 'recurring_payment_id': recurring_payment_id})
|
|
47
|
+
raw = self._transport.request('PATCH', 'recurring-payment/activate', body)
|
|
48
|
+
return RecurringPayment.from_dict(raw.get('data') or {})
|
|
49
|
+
|
|
50
|
+
def delete(self, sid: str, recurring_payment_id: int | str) -> MessageResult:
|
|
51
|
+
body = self._envelope({'sid': sid, 'recurring_payment_id': recurring_payment_id})
|
|
52
|
+
raw = self._transport.request('POST', 'recurring-payment/delete', body)
|
|
53
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.message_result import MessageResult
|
|
6
|
+
from ..data_objects.tokenize_result import TokenizeResult
|
|
7
|
+
from ..data_objects.transaction import Transaction
|
|
8
|
+
from ..pagination.cursor_page import CursorPage
|
|
9
|
+
from .abstract_resource import AbstractResource
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Transactions(AbstractResource):
|
|
13
|
+
def list(self, sid: str, filters: dict[str, Any] | None = None) -> CursorPage[Transaction]:
|
|
14
|
+
body = self._envelope({'sid': sid}, filters or {})
|
|
15
|
+
return self._paginate('GET', 'transactions', body, Transaction.from_dict)
|
|
16
|
+
|
|
17
|
+
def show(self, sid: str, identifier: dict[str, Any]) -> Transaction:
|
|
18
|
+
raw = self._transport.request('GET', 'transaction', self._envelope({'sid': sid} | identifier))
|
|
19
|
+
return Transaction.from_dict(raw.get('data') or {})
|
|
20
|
+
|
|
21
|
+
def charge_card(self, sid: str, attributes: dict[str, Any] | None = None) -> Transaction:
|
|
22
|
+
body = self._envelope({'sid': sid} | (attributes or {}))
|
|
23
|
+
raw = self._transport.request('POST', 'transaction/charge-card', body)
|
|
24
|
+
return Transaction.from_dict(raw.get('data') or {})
|
|
25
|
+
|
|
26
|
+
def charge_card_token(self, sid: str, attributes: dict[str, Any] | None = None) -> Transaction:
|
|
27
|
+
body = self._envelope({'sid': sid} | (attributes or {}))
|
|
28
|
+
raw = self._transport.request('POST', 'transaction/charge-card-token', body)
|
|
29
|
+
return Transaction.from_dict(raw.get('data') or {})
|
|
30
|
+
|
|
31
|
+
def charge_ach(self, sid: str, attributes: dict[str, Any] | None = None) -> Transaction:
|
|
32
|
+
body = self._envelope({'sid': sid} | (attributes or {}))
|
|
33
|
+
raw = self._transport.request('POST', 'transaction/charge-ach', body)
|
|
34
|
+
return Transaction.from_dict(raw.get('data') or {})
|
|
35
|
+
|
|
36
|
+
def tokenize_card(self, sid: str, attributes: dict[str, Any] | None = None) -> TokenizeResult:
|
|
37
|
+
body = self._envelope({'sid': sid} | (attributes or {}))
|
|
38
|
+
raw = self._transport.request('POST', 'transaction/tokenize-card', body)
|
|
39
|
+
return TokenizeResult.from_dict(raw.get('data') or {})
|
|
40
|
+
|
|
41
|
+
def refund(self, sid: str, attributes: dict[str, Any] | None = None) -> MessageResult:
|
|
42
|
+
body = self._envelope({'sid': sid} | (attributes or {}))
|
|
43
|
+
raw = self._transport.request('POST', 'transaction/refund', body)
|
|
44
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
45
|
+
|
|
46
|
+
def void(self, sid: str, transaction_type: str, transaction_id: int | str) -> MessageResult:
|
|
47
|
+
body = self._envelope({
|
|
48
|
+
'sid': sid,
|
|
49
|
+
'transaction_type': transaction_type,
|
|
50
|
+
'transaction_id': transaction_id,
|
|
51
|
+
})
|
|
52
|
+
raw = self._transport.request('PATCH', 'transaction/void', body)
|
|
53
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
File without changes
|