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.
Files changed (48) hide show
  1. dime_payments/__init__.py +29 -0
  2. dime_payments/client.py +30 -0
  3. dime_payments/config.py +32 -0
  4. dime_payments/data_objects/__init__.py +31 -0
  5. dime_payments/data_objects/address.py +30 -0
  6. dime_payments/data_objects/customer.py +37 -0
  7. dime_payments/data_objects/deposit.py +31 -0
  8. dime_payments/data_objects/deposit_group.py +25 -0
  9. dime_payments/data_objects/deposit_with_transactions.py +37 -0
  10. dime_payments/data_objects/form_link.py +13 -0
  11. dime_payments/data_objects/merchant.py +55 -0
  12. dime_payments/data_objects/message_result.py +13 -0
  13. dime_payments/data_objects/payment_method.py +63 -0
  14. dime_payments/data_objects/recurring_payment.py +53 -0
  15. dime_payments/data_objects/recurring_payment_method.py +17 -0
  16. dime_payments/data_objects/tokenize_result.py +13 -0
  17. dime_payments/data_objects/transaction.py +57 -0
  18. dime_payments/data_objects/transaction_address.py +27 -0
  19. dime_payments/exceptions/__init__.py +21 -0
  20. dime_payments/exceptions/api_exception.py +5 -0
  21. dime_payments/exceptions/authentication_exception.py +5 -0
  22. dime_payments/exceptions/connection_exception.py +5 -0
  23. dime_payments/exceptions/dime_exception.py +19 -0
  24. dime_payments/exceptions/not_found_exception.py +5 -0
  25. dime_payments/exceptions/permission_denied_exception.py +5 -0
  26. dime_payments/exceptions/rate_limit_exception.py +18 -0
  27. dime_payments/exceptions/server_exception.py +5 -0
  28. dime_payments/exceptions/validation_exception.py +24 -0
  29. dime_payments/http/__init__.py +0 -0
  30. dime_payments/http/error_handler.py +76 -0
  31. dime_payments/http/transport.py +80 -0
  32. dime_payments/pagination/__init__.py +3 -0
  33. dime_payments/pagination/cursor_page.py +72 -0
  34. dime_payments/resources/__init__.py +0 -0
  35. dime_payments/resources/abstract_resource.py +49 -0
  36. dime_payments/resources/addresses.py +37 -0
  37. dime_payments/resources/customers.py +32 -0
  38. dime_payments/resources/deposits.py +31 -0
  39. dime_payments/resources/merchants.py +31 -0
  40. dime_payments/resources/payment_methods.py +37 -0
  41. dime_payments/resources/recurring_payments.py +53 -0
  42. dime_payments/resources/transactions.py +53 -0
  43. dime_payments/support/__init__.py +0 -0
  44. dime_payments/support/arr.py +60 -0
  45. dime_python_sdk-1.0.0.dist-info/METADATA +288 -0
  46. dime_python_sdk-1.0.0.dist-info/RECORD +48 -0
  47. dime_python_sdk-1.0.0.dist-info/WHEEL +4 -0
  48. dime_python_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,5 @@
1
+ from .dime_exception import DimeException
2
+
3
+
4
+ class NotFoundException(DimeException):
5
+ pass
@@ -0,0 +1,5 @@
1
+ from .dime_exception import DimeException
2
+
3
+
4
+ class PermissionDeniedException(DimeException):
5
+ pass
@@ -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,5 @@
1
+ from .dime_exception import DimeException
2
+
3
+
4
+ class ServerException(DimeException):
5
+ pass
@@ -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,3 @@
1
+ from .cursor_page import CursorPage
2
+
3
+ __all__ = ['CursorPage']
@@ -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