dime-python-sdk 1.3.0__py3-none-any.whl → 1.4.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 (29) hide show
  1. dime_payments/client.py +10 -0
  2. dime_payments/config.py +1 -1
  3. dime_payments/data_objects/__init__.py +27 -0
  4. dime_payments/data_objects/application_status.py +38 -0
  5. dime_payments/data_objects/chargeback.py +63 -0
  6. dime_payments/data_objects/document.py +49 -0
  7. dime_payments/data_objects/document_upload_result.py +42 -0
  8. dime_payments/data_objects/fund_release.py +63 -0
  9. dime_payments/data_objects/held_balance.py +50 -0
  10. dime_payments/data_objects/releasable_transactions.py +57 -0
  11. dime_payments/data_objects/subscribe_result.py +29 -0
  12. dime_payments/data_objects/subscription.py +61 -0
  13. dime_payments/data_objects/subscription_item.py +29 -0
  14. dime_payments/data_objects/subscription_payment_method.py +31 -0
  15. dime_payments/data_objects/subscription_plan.py +46 -0
  16. dime_payments/http/error_handler.py +18 -5
  17. dime_payments/http/transport.py +40 -2
  18. dime_payments/resources/chargebacks.py +34 -0
  19. dime_payments/resources/documents.py +81 -0
  20. dime_payments/resources/funds.py +77 -0
  21. dime_payments/resources/invoices.py +8 -1
  22. dime_payments/resources/merchants.py +10 -0
  23. dime_payments/resources/subscription_plans.py +97 -0
  24. dime_payments/resources/subscriptions.py +53 -0
  25. dime_payments/resources/transactions.py +31 -0
  26. {dime_python_sdk-1.3.0.dist-info → dime_python_sdk-1.4.0.dist-info}/METADATA +141 -4
  27. {dime_python_sdk-1.3.0.dist-info → dime_python_sdk-1.4.0.dist-info}/RECORD +29 -12
  28. {dime_python_sdk-1.3.0.dist-info → dime_python_sdk-1.4.0.dist-info}/WHEEL +1 -1
  29. {dime_python_sdk-1.3.0.dist-info → dime_python_sdk-1.4.0.dist-info}/licenses/LICENSE +0 -0
dime_payments/client.py CHANGED
@@ -1,13 +1,18 @@
1
1
  from .config import Config
2
2
  from .http.transport import Transport
3
3
  from .resources.addresses import Addresses
4
+ from .resources.chargebacks import Chargebacks
4
5
  from .resources.customers import Customers
5
6
  from .resources.deposits import Deposits
7
+ from .resources.documents import Documents
8
+ from .resources.funds import Funds
6
9
  from .resources.invoices import Invoices
7
10
  from .resources.merchants import Merchants
8
11
  from .resources.payment_methods import PaymentMethods
9
12
  from .resources.recurring_invoices import RecurringInvoices
10
13
  from .resources.recurring_payments import RecurringPayments
14
+ from .resources.subscription_plans import SubscriptionPlans
15
+ from .resources.subscriptions import Subscriptions
11
16
  from .resources.transactions import Transactions
12
17
 
13
18
 
@@ -29,6 +34,11 @@ class Client:
29
34
  self.recurring_payments = RecurringPayments(transport)
30
35
  self.invoices = Invoices(transport)
31
36
  self.recurring_invoices = RecurringInvoices(transport)
37
+ self.chargebacks = Chargebacks(transport)
38
+ self.documents = Documents(transport)
39
+ self.funds = Funds(transport)
40
+ self.subscription_plans = SubscriptionPlans(transport)
41
+ self.subscriptions = Subscriptions(transport)
32
42
 
33
43
  def config(self) -> Config:
34
44
  return self._config
dime_payments/config.py CHANGED
@@ -8,7 +8,7 @@ if TYPE_CHECKING:
8
8
 
9
9
  class Config:
10
10
  DEFAULT_BASE_URL = 'https://app.dimepayments.com'
11
- VERSION = '1.3.0'
11
+ VERSION = '1.4.0'
12
12
 
13
13
  def __init__(
14
14
  self,
@@ -1,9 +1,15 @@
1
1
  from .address import Address
2
+ from .application_status import ApplicationStatus
3
+ from .chargeback import Chargeback
2
4
  from .customer import Customer
3
5
  from .deposit import Deposit
4
6
  from .deposit_group import DepositGroup
5
7
  from .deposit_with_transactions import DepositWithTransactions
8
+ from .document import Document
9
+ from .document_upload_result import DocumentUploadFailure, DocumentUploadResult
6
10
  from .form_link import FormLink
11
+ from .fund_release import FundRelease, FundReleaseResult
12
+ from .held_balance import HeldBalance
7
13
  from .invoice import Invoice
8
14
  from .invoice_customer import InvoiceCustomer
9
15
  from .invoice_event import InvoiceEvent
@@ -17,6 +23,12 @@ from .payment_method import PaymentMethod
17
23
  from .recurring_invoice import RecurringInvoice, RecurringInvoiceRun
18
24
  from .recurring_payment import RecurringPayment
19
25
  from .recurring_payment_method import RecurringPaymentMethod
26
+ from .releasable_transactions import ReleasableTransaction, ReleasableTransactions
27
+ from .subscribe_result import SubscribeResult
28
+ from .subscription import Subscription
29
+ from .subscription_item import SubscriptionItem
30
+ from .subscription_payment_method import SubscriptionPaymentMethod
31
+ from .subscription_plan import SubscriptionPlan
20
32
  from .tokenize_result import TokenizeResult
21
33
  from .transaction import Transaction
22
34
  from .transaction_address import TransactionAddress
@@ -27,6 +39,7 @@ __all__ = [
27
39
  'Customer',
28
40
  'PaymentMethod',
29
41
  'Merchant',
42
+ 'ApplicationStatus',
30
43
  'Address',
31
44
  'Deposit',
32
45
  'DepositGroup',
@@ -45,4 +58,18 @@ __all__ = [
45
58
  'LineItem',
46
59
  'RecurringInvoice',
47
60
  'RecurringInvoiceRun',
61
+ 'Chargeback',
62
+ 'Document',
63
+ 'DocumentUploadResult',
64
+ 'DocumentUploadFailure',
65
+ 'HeldBalance',
66
+ 'ReleasableTransaction',
67
+ 'ReleasableTransactions',
68
+ 'FundRelease',
69
+ 'FundReleaseResult',
70
+ 'SubscriptionPlan',
71
+ 'Subscription',
72
+ 'SubscriptionItem',
73
+ 'SubscriptionPaymentMethod',
74
+ 'SubscribeResult',
48
75
  ]
@@ -0,0 +1,38 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_bool, arr_string
5
+
6
+
7
+ @dataclass
8
+ class ApplicationStatus:
9
+ """
10
+ Where a merchant sits in onboarding.
11
+
12
+ ``status`` is the headline: one of ``lead``, ``discovery``, ``proposal``,
13
+ ``application_in_progress``, ``underwriting``, ``live``,
14
+ ``cancellation_pending``, ``churned`` or ``declined`` (``None`` if onboarding
15
+ has not started). ``application_status`` is the underlying application —
16
+ ``draft``, ``pending_review``, ``submitted``, ``approved``,
17
+ ``needs_documents``, ``failed`` or ``None`` — and is the field to read while
18
+ ``status`` is ``underwriting``, since only ``needs_documents`` asks you to
19
+ act. ``boarded`` is the ground truth for whether the merchant can take money.
20
+ """
21
+
22
+ sid: str | None = None
23
+ name: str | None = None
24
+ status: str | None = None
25
+ application_status: str | None = None
26
+ boarded: bool = False
27
+ application_submitted_at: str | None = None
28
+
29
+ @classmethod
30
+ def from_dict(cls, data: dict[str, Any]) -> 'ApplicationStatus':
31
+ return cls(
32
+ sid=arr_string(data, 'sid'),
33
+ name=arr_string(data, 'name'),
34
+ status=arr_string(data, 'status'),
35
+ application_status=arr_string(data, 'application_status'),
36
+ boarded=arr_bool(data, 'boarded'),
37
+ application_submitted_at=arr_string(data, 'application_submitted_at'),
38
+ )
@@ -0,0 +1,63 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_bool, arr_int, arr_string
5
+
6
+
7
+ @dataclass
8
+ class Chargeback:
9
+ """
10
+ A chargeback raised against a merchant. The same field set the chargeback
11
+ webhooks carry.
12
+
13
+ ``transaction_info_id`` is the processor's stable identifier for the
14
+ chargeback; ``parent_transaction_info_id`` identifies the disputed payment.
15
+ ``representment_status`` and ``result`` are free text from the processor;
16
+ ``resolved`` says whether the dispute has reached a terminal state.
17
+ """
18
+
19
+ transaction_info_id: str | None = None
20
+ parent_transaction_info_id: str | None = None
21
+ gateway_transaction_id: str | None = None
22
+ transaction_number: str | None = None
23
+ invoice_number: str | None = None
24
+ chargeback_date: str | None = None
25
+ merchant_chargeback_date: str | None = None
26
+ transaction_amount: str | None = None
27
+ chargeback_amount: str | None = None
28
+ card_brand: str | None = None
29
+ cc_last_four: str | None = None
30
+ payee_name: str | None = None
31
+ days_to_represent: int | None = None
32
+ representment_date: str | None = None
33
+ merchant_representment_date: str | None = None
34
+ representment_status: str | None = None
35
+ result: str | None = None
36
+ chargeback_code: str | None = None
37
+ chargeback_response_code: str | None = None
38
+ resolved: bool = False
39
+
40
+ @classmethod
41
+ def from_dict(cls, data: dict[str, Any]) -> 'Chargeback':
42
+ return cls(
43
+ transaction_info_id=arr_string(data, 'transaction_info_id'),
44
+ parent_transaction_info_id=arr_string(data, 'parent_transaction_info_id'),
45
+ gateway_transaction_id=arr_string(data, 'gateway_transaction_id'),
46
+ transaction_number=arr_string(data, 'transaction_number'),
47
+ invoice_number=arr_string(data, 'invoice_number'),
48
+ chargeback_date=arr_string(data, 'chargeback_date'),
49
+ merchant_chargeback_date=arr_string(data, 'merchant_chargeback_date'),
50
+ transaction_amount=arr_string(data, 'transaction_amount'),
51
+ chargeback_amount=arr_string(data, 'chargeback_amount'),
52
+ card_brand=arr_string(data, 'card_brand'),
53
+ cc_last_four=arr_string(data, 'cc_last_four'),
54
+ payee_name=arr_string(data, 'payee_name'),
55
+ days_to_represent=arr_int(data, 'days_to_represent'),
56
+ representment_date=arr_string(data, 'representment_date'),
57
+ merchant_representment_date=arr_string(data, 'merchant_representment_date'),
58
+ representment_status=arr_string(data, 'representment_status'),
59
+ result=arr_string(data, 'result'),
60
+ chargeback_code=arr_string(data, 'chargeback_code'),
61
+ chargeback_response_code=arr_string(data, 'chargeback_response_code'),
62
+ resolved=arr_bool(data, 'resolved'),
63
+ )
@@ -0,0 +1,49 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_int, arr_string
5
+
6
+
7
+ @dataclass
8
+ class Document:
9
+ """
10
+ A document held for a merchant.
11
+
12
+ ``doc_type`` is one of ``Verification``, ``FraudHolds``, ``Underwriting`` or
13
+ ``RetrievalRequest``. ``uploaded_via`` is ``api`` for documents sent through
14
+ :meth:`Documents.upload`. The upload response carries only ``uuid``,
15
+ ``file_name``, ``doc_type`` and ``size``; the rest are filled by
16
+ :meth:`Documents.list`.
17
+
18
+ ``sent_to_processor_at`` and ``processor_status`` record when our team
19
+ forwarded the document to the processor and what it answered. Both are
20
+ ``None`` until it has been forwarded.
21
+ """
22
+
23
+ uuid: str | None = None
24
+ file_name: str | None = None
25
+ doc_type: str | None = None
26
+ chargeback_transaction_info_id: str | None = None
27
+ size: int | None = None
28
+ uploaded_at: str | None = None
29
+ uploaded_via: str | None = None
30
+ sent_to_processor_at: str | None = None
31
+ processor_status: str | None = None
32
+
33
+ @classmethod
34
+ def from_dict(cls, data: dict[str, Any]) -> 'Document':
35
+ # The API reports forwarding as a [timestamp, processor status] pair.
36
+ sent = data.get('sent_to_processor')
37
+ pair = dict(zip(('at', 'status'), sent)) if isinstance(sent, list) else {'at': sent}
38
+
39
+ return cls(
40
+ uuid=arr_string(data, 'uuid'),
41
+ file_name=arr_string(data, 'file_name'),
42
+ doc_type=arr_string(data, 'doc_type'),
43
+ chargeback_transaction_info_id=arr_string(data, 'chargeback_transaction_info_id'),
44
+ size=arr_int(data, 'size'),
45
+ uploaded_at=arr_string(data, 'uploaded_at'),
46
+ uploaded_via=arr_string(data, 'uploaded_via'),
47
+ sent_to_processor_at=arr_string(pair, 'at'),
48
+ processor_status=arr_string(pair, 'status'),
49
+ )
@@ -0,0 +1,42 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_array, arr_string
5
+ from .document import Document
6
+
7
+
8
+ @dataclass
9
+ class DocumentUploadFailure:
10
+ """A file from an upload that could not be stored. Re-send just this one."""
11
+
12
+ file_name: str | None = None
13
+ reason: str | None = None
14
+
15
+ @classmethod
16
+ def from_dict(cls, data: dict[str, Any]) -> 'DocumentUploadFailure':
17
+ return cls(
18
+ file_name=arr_string(data, 'file_name'),
19
+ reason=arr_string(data, 'reason'),
20
+ )
21
+
22
+
23
+ @dataclass
24
+ class DocumentUploadResult:
25
+ """
26
+ The outcome of :meth:`Documents.upload`.
27
+
28
+ Files are stored independently, so an upload can partly succeed: ``documents``
29
+ lists what was stored and ``failed`` what was not.
30
+ """
31
+
32
+ message: str | None = None
33
+ documents: list[Document] = field(default_factory=list)
34
+ failed: list[DocumentUploadFailure] = field(default_factory=list)
35
+
36
+ @classmethod
37
+ def from_dict(cls, data: dict[str, Any]) -> 'DocumentUploadResult':
38
+ return cls(
39
+ message=arr_string(data, 'message'),
40
+ documents=[Document.from_dict(d) for d in arr_array(data, 'documents') if isinstance(d, dict)],
41
+ failed=[DocumentUploadFailure.from_dict(f) for f in arr_array(data, 'failed') if isinstance(f, dict)],
42
+ )
@@ -0,0 +1,63 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_array, arr_bool, arr_int, arr_object, arr_string
5
+
6
+
7
+ @dataclass
8
+ class FundRelease:
9
+ """
10
+ A request to pay out held funds.
11
+
12
+ ``status`` is ``released`` (sent), ``failed`` (declined; nothing moved) or
13
+ ``unknown`` (no confirmation came back, so it may have gone through — its
14
+ amount stays held back from ``releasable`` until it is reconciled).
15
+ ``transaction_info_ids`` is empty when the release was requested by amount.
16
+ """
17
+
18
+ id: int | None = None
19
+ amount: str | None = None
20
+ fee: str | None = None
21
+ status: str | None = None
22
+ status_label: str | None = None
23
+ idempotency_key: str | None = None
24
+ transaction_info_ids: list[str] = field(default_factory=list)
25
+ failure_reason: str | None = None
26
+ requested_at: str | None = None
27
+ completed_at: str | None = None
28
+
29
+ @classmethod
30
+ def from_dict(cls, data: dict[str, Any]) -> 'FundRelease':
31
+ return cls(
32
+ id=arr_int(data, 'id'),
33
+ amount=arr_string(data, 'amount'),
34
+ fee=arr_string(data, 'fee'),
35
+ status=arr_string(data, 'status'),
36
+ status_label=arr_string(data, 'status_label'),
37
+ idempotency_key=arr_string(data, 'idempotency_key'),
38
+ transaction_info_ids=[str(t) for t in arr_array(data, 'transaction_info_ids') if t is not None],
39
+ failure_reason=arr_string(data, 'failure_reason'),
40
+ requested_at=arr_string(data, 'requested_at'),
41
+ completed_at=arr_string(data, 'completed_at'),
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class FundReleaseResult:
47
+ """
48
+ The response to :meth:`Funds.release`. ``replayed`` is true when the
49
+ ``idempotency_key`` had been used before and the original release is being
50
+ returned rather than a new one sent.
51
+ """
52
+
53
+ sid: str | None = None
54
+ replayed: bool = False
55
+ release: FundRelease = field(default_factory=FundRelease)
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: dict[str, Any]) -> 'FundReleaseResult':
59
+ return cls(
60
+ sid=arr_string(data, 'sid'),
61
+ replayed=arr_bool(data, 'replayed'),
62
+ release=FundRelease.from_dict(arr_object(data, 'release')),
63
+ )
@@ -0,0 +1,50 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_bool, arr_int, arr_string
5
+
6
+
7
+ @dataclass
8
+ class HeldBalance:
9
+ """
10
+ A held-funds merchant's balance at the processor, in dollars.
11
+
12
+ ``releasable`` is the figure that matters and the one a release is checked
13
+ against: ``available`` less ``at_risk`` (ACH still inside the return window),
14
+ ``owed_to_split`` (takings promised to another account), ``unresolved``
15
+ (releases requested and not yet confirmed) and ``release_fee``, never below
16
+ zero and never above ``ach_out_limit_remaining``.
17
+
18
+ Money is kept as strings, consistent with the rest of the SDK, to avoid float
19
+ rounding.
20
+ """
21
+
22
+ sid: str | None = None
23
+ available: str | None = None
24
+ pending: str | None = None
25
+ reserve: str | None = None
26
+ at_risk: str | None = None
27
+ owed_to_split: str | None = None
28
+ unresolved: str | None = None
29
+ release_fee: str | None = None
30
+ releasable: str | None = None
31
+ ach_settlement_days: int | None = None
32
+ ach_out_enabled: bool = False
33
+ ach_out_limit_remaining: str | None = None
34
+
35
+ @classmethod
36
+ def from_dict(cls, data: dict[str, Any]) -> 'HeldBalance':
37
+ return cls(
38
+ sid=arr_string(data, 'sid'),
39
+ available=arr_string(data, 'available'),
40
+ pending=arr_string(data, 'pending'),
41
+ reserve=arr_string(data, 'reserve'),
42
+ at_risk=arr_string(data, 'at_risk'),
43
+ owed_to_split=arr_string(data, 'owed_to_split'),
44
+ unresolved=arr_string(data, 'unresolved'),
45
+ release_fee=arr_string(data, 'release_fee'),
46
+ releasable=arr_string(data, 'releasable'),
47
+ ach_settlement_days=arr_int(data, 'ach_settlement_days'),
48
+ ach_out_enabled=arr_bool(data, 'ach_out_enabled'),
49
+ ach_out_limit_remaining=arr_string(data, 'ach_out_limit_remaining'),
50
+ )
@@ -0,0 +1,57 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_array, arr_bool, arr_string
5
+
6
+
7
+ @dataclass
8
+ class ReleasableTransaction:
9
+ """
10
+ A held-funds payment that can be released now. ``amount`` is what releasing
11
+ it would pay the merchant: ``net_amount`` less ``split_amount``. ``type`` is
12
+ ``CC`` or ``ACH``.
13
+ """
14
+
15
+ transaction_info_id: str | None = None
16
+ type: str | None = None
17
+ transaction_date: str | None = None
18
+ gross_amount: str | None = None
19
+ net_amount: str | None = None
20
+ split_amount: str | None = None
21
+ amount: str | None = None
22
+
23
+ @classmethod
24
+ def from_dict(cls, data: dict[str, Any]) -> 'ReleasableTransaction':
25
+ return cls(
26
+ transaction_info_id=arr_string(data, 'transaction_info_id'),
27
+ type=arr_string(data, 'type'),
28
+ transaction_date=arr_string(data, 'transaction_date'),
29
+ gross_amount=arr_string(data, 'gross_amount'),
30
+ net_amount=arr_string(data, 'net_amount'),
31
+ split_amount=arr_string(data, 'split_amount'),
32
+ amount=arr_string(data, 'amount'),
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class ReleasableTransactions:
38
+ """
39
+ The payments a held-funds merchant can release, newest first, up to 500.
40
+ ``truncated`` is true when there are more than were listed.
41
+ """
42
+
43
+ sid: str | None = None
44
+ transactions: list[ReleasableTransaction] = field(default_factory=list)
45
+ total: str | None = None
46
+ truncated: bool = False
47
+
48
+ @classmethod
49
+ def from_dict(cls, data: dict[str, Any]) -> 'ReleasableTransactions':
50
+ return cls(
51
+ sid=arr_string(data, 'sid'),
52
+ transactions=[
53
+ ReleasableTransaction.from_dict(t) for t in arr_array(data, 'transactions') if isinstance(t, dict)
54
+ ],
55
+ total=arr_string(data, 'total'),
56
+ truncated=arr_bool(data, 'truncated'),
57
+ )
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_int, arr_string
5
+
6
+
7
+ @dataclass
8
+ class SubscribeResult:
9
+ """
10
+ The response to :meth:`SubscriptionPlans.subscribe`: the new subscription's
11
+ id and status, and the first payment that was charged to start it.
12
+ Fetch the full record with :meth:`Subscriptions.show`.
13
+ """
14
+
15
+ subscription_id: int | None = None
16
+ status: str | None = None
17
+ next_run_date: str | None = None
18
+ transaction_number: str | None = None
19
+ amount: str | None = None
20
+
21
+ @classmethod
22
+ def from_dict(cls, data: dict[str, Any]) -> 'SubscribeResult':
23
+ return cls(
24
+ subscription_id=arr_int(data, 'subscription_id'),
25
+ status=arr_string(data, 'status'),
26
+ next_run_date=arr_string(data, 'next_run_date'),
27
+ transaction_number=arr_string(data, 'transaction_number'),
28
+ amount=arr_string(data, 'amount'),
29
+ )
@@ -0,0 +1,61 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_array, arr_int, arr_object, arr_string
5
+ from .subscription_item import SubscriptionItem
6
+ from .subscription_payment_method import SubscriptionPaymentMethod
7
+
8
+
9
+ @dataclass
10
+ class Subscription:
11
+ """
12
+ A customer's enrollment in a :class:`SubscriptionPlan`.
13
+
14
+ ``status`` is one of ``Active``, ``Failed``, ``Paused``, ``Cancelled`` or
15
+ ``Ended``. ``items`` is populated by :meth:`Subscriptions.show` only; list
16
+ and lifecycle responses leave it empty.
17
+ """
18
+
19
+ id: int | None = None
20
+ subscription_plan_id: int | None = None
21
+ plan_name: str | None = None
22
+ amount: str | None = None
23
+ recurrence_schedule: str | None = None
24
+ start_date: str | None = None
25
+ end_date: str | None = None
26
+ last_run_date: str | None = None
27
+ last_run_status: str | None = None
28
+ last_run_failed_count: int | None = None
29
+ next_run_date: str | None = None
30
+ status: str | None = None
31
+ paused_until_date: str | None = None
32
+ cancelled_at: str | None = None
33
+ cancelled_by: str | None = None
34
+ customer_uuid: str | None = None
35
+ error: str | None = None
36
+ payment_method: SubscriptionPaymentMethod = field(default_factory=SubscriptionPaymentMethod)
37
+ items: list[SubscriptionItem] = field(default_factory=list)
38
+
39
+ @classmethod
40
+ def from_dict(cls, data: dict[str, Any]) -> 'Subscription':
41
+ return cls(
42
+ id=arr_int(data, 'id'),
43
+ subscription_plan_id=arr_int(data, 'subscription_plan_id'),
44
+ plan_name=arr_string(data, 'plan_name'),
45
+ amount=arr_string(data, 'amount'),
46
+ recurrence_schedule=arr_string(data, 'recurrence_schedule'),
47
+ start_date=arr_string(data, 'start_date'),
48
+ end_date=arr_string(data, 'end_date'),
49
+ last_run_date=arr_string(data, 'last_run_date'),
50
+ last_run_status=arr_string(data, 'last_run_status'),
51
+ last_run_failed_count=arr_int(data, 'last_run_failed_count'),
52
+ next_run_date=arr_string(data, 'next_run_date'),
53
+ status=arr_string(data, 'status'),
54
+ paused_until_date=arr_string(data, 'paused_until_date'),
55
+ cancelled_at=arr_string(data, 'cancelled_at'),
56
+ cancelled_by=arr_string(data, 'cancelled_by'),
57
+ customer_uuid=arr_string(data, 'customer_uuid'),
58
+ error=arr_string(data, 'error'),
59
+ payment_method=SubscriptionPaymentMethod.from_dict(arr_object(data, 'payment_method')),
60
+ items=[SubscriptionItem.from_dict(i) for i in arr_array(data, 'items') if isinstance(i, dict)],
61
+ )
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_string
5
+
6
+
7
+ @dataclass
8
+ class SubscriptionItem:
9
+ """
10
+ A line on a subscription plan or subscription. ``amount`` (quantity times
11
+ unit price) is reported on a :class:`Subscription`'s items only; it is
12
+ ``None`` on a :class:`SubscriptionPlan`'s.
13
+ """
14
+
15
+ name: str | None = None
16
+ description: str | None = None
17
+ quantity: str | None = None
18
+ unit_price: str | None = None
19
+ amount: str | None = None
20
+
21
+ @classmethod
22
+ def from_dict(cls, data: dict[str, Any]) -> 'SubscriptionItem':
23
+ return cls(
24
+ name=arr_string(data, 'name'),
25
+ description=arr_string(data, 'description'),
26
+ quantity=arr_string(data, 'quantity'),
27
+ unit_price=arr_string(data, 'unit_price'),
28
+ amount=arr_string(data, 'amount'),
29
+ )
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_int, arr_string
5
+
6
+
7
+ @dataclass
8
+ class SubscriptionPaymentMethod:
9
+ """
10
+ The saved payment method a subscription charges. ``type`` is ``cc`` or
11
+ ``ach``; ``last_four`` and ``expiration`` are set for a card, ``bank_name``
12
+ and ``account_type`` for a bank account.
13
+ """
14
+
15
+ id: int | None = None
16
+ type: str | None = None
17
+ last_four: str | None = None
18
+ expiration: str | None = None
19
+ bank_name: str | None = None
20
+ account_type: str | None = None
21
+
22
+ @classmethod
23
+ def from_dict(cls, data: dict[str, Any]) -> 'SubscriptionPaymentMethod':
24
+ return cls(
25
+ id=arr_int(data, 'id'),
26
+ type=arr_string(data, 'type'),
27
+ last_four=arr_string(data, 'last_four'),
28
+ expiration=arr_string(data, 'expiration'),
29
+ bank_name=arr_string(data, 'bank_name'),
30
+ account_type=arr_string(data, 'account_type'),
31
+ )
@@ -0,0 +1,46 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_array, arr_bool, arr_int, arr_string
5
+ from .subscription_item import SubscriptionItem
6
+
7
+
8
+ @dataclass
9
+ class SubscriptionPlan:
10
+ """
11
+ A recurring offering customers subscribe to.
12
+
13
+ ``status`` is ``draft``, ``active`` or ``archived``; only an active plan
14
+ accepts new subscribers. ``recurrence_schedule`` is one of ``Weekly``,
15
+ ``Biweekly``, ``FirstFifteenth``, ``Monthly`` or ``Yearly``.
16
+ """
17
+
18
+ id: int | None = None
19
+ name: str | None = None
20
+ description: str | None = None
21
+ recurrence_schedule: str | None = None
22
+ status: str | None = None
23
+ subtotal: str | None = None
24
+ total: str | None = None
25
+ token: str | None = None
26
+ public_url: str | None = None
27
+ allow_public: bool = False
28
+ created_at: str | None = None
29
+ items: list[SubscriptionItem] = field(default_factory=list)
30
+
31
+ @classmethod
32
+ def from_dict(cls, data: dict[str, Any]) -> 'SubscriptionPlan':
33
+ return cls(
34
+ id=arr_int(data, 'id'),
35
+ name=arr_string(data, 'name'),
36
+ description=arr_string(data, 'description'),
37
+ recurrence_schedule=arr_string(data, 'recurrence_schedule'),
38
+ status=arr_string(data, 'status'),
39
+ subtotal=arr_string(data, 'subtotal'),
40
+ total=arr_string(data, 'total'),
41
+ token=arr_string(data, 'token'),
42
+ public_url=arr_string(data, 'public_url'),
43
+ allow_public=arr_bool(data, 'allow_public'),
44
+ created_at=arr_string(data, 'created_at'),
45
+ items=[SubscriptionItem.from_dict(i) for i in arr_array(data, 'items') if isinstance(i, dict)],
46
+ )