dime-python-sdk 1.0.0__py3-none-any.whl → 1.2.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/client.py +4 -0
- dime_payments/config.py +1 -1
- dime_payments/data_objects/__init__.py +17 -0
- dime_payments/data_objects/invoice.py +62 -0
- dime_payments/data_objects/invoice_customer.py +21 -0
- dime_payments/data_objects/invoice_event.py +23 -0
- dime_payments/data_objects/invoice_item.py +29 -0
- dime_payments/data_objects/invoice_link.py +19 -0
- dime_payments/data_objects/invoice_payment.py +23 -0
- dime_payments/data_objects/line_item.py +33 -0
- dime_payments/data_objects/recurring_invoice.py +67 -0
- dime_payments/http/transport.py +1 -1
- dime_payments/resources/invoices.py +144 -0
- dime_payments/resources/recurring_invoices.py +28 -0
- {dime_python_sdk-1.0.0.dist-info → dime_python_sdk-1.2.0.dist-info}/METADATA +83 -2
- {dime_python_sdk-1.0.0.dist-info → dime_python_sdk-1.2.0.dist-info}/RECORD +18 -8
- {dime_python_sdk-1.0.0.dist-info → dime_python_sdk-1.2.0.dist-info}/WHEEL +1 -1
- {dime_python_sdk-1.0.0.dist-info → dime_python_sdk-1.2.0.dist-info}/licenses/LICENSE +0 -0
dime_payments/client.py
CHANGED
|
@@ -3,8 +3,10 @@ from .http.transport import Transport
|
|
|
3
3
|
from .resources.addresses import Addresses
|
|
4
4
|
from .resources.customers import Customers
|
|
5
5
|
from .resources.deposits import Deposits
|
|
6
|
+
from .resources.invoices import Invoices
|
|
6
7
|
from .resources.merchants import Merchants
|
|
7
8
|
from .resources.payment_methods import PaymentMethods
|
|
9
|
+
from .resources.recurring_invoices import RecurringInvoices
|
|
8
10
|
from .resources.recurring_payments import RecurringPayments
|
|
9
11
|
from .resources.transactions import Transactions
|
|
10
12
|
|
|
@@ -25,6 +27,8 @@ class Client:
|
|
|
25
27
|
self.addresses = Addresses(transport)
|
|
26
28
|
self.deposits = Deposits(transport)
|
|
27
29
|
self.recurring_payments = RecurringPayments(transport)
|
|
30
|
+
self.invoices = Invoices(transport)
|
|
31
|
+
self.recurring_invoices = RecurringInvoices(transport)
|
|
28
32
|
|
|
29
33
|
def config(self) -> Config:
|
|
30
34
|
return self._config
|
dime_payments/config.py
CHANGED
|
@@ -4,9 +4,17 @@ from .deposit import Deposit
|
|
|
4
4
|
from .deposit_group import DepositGroup
|
|
5
5
|
from .deposit_with_transactions import DepositWithTransactions
|
|
6
6
|
from .form_link import FormLink
|
|
7
|
+
from .invoice import Invoice
|
|
8
|
+
from .invoice_customer import InvoiceCustomer
|
|
9
|
+
from .invoice_event import InvoiceEvent
|
|
10
|
+
from .invoice_item import InvoiceItem
|
|
11
|
+
from .invoice_link import InvoiceLink
|
|
12
|
+
from .invoice_payment import InvoicePayment
|
|
13
|
+
from .line_item import LineItem
|
|
7
14
|
from .merchant import Merchant
|
|
8
15
|
from .message_result import MessageResult
|
|
9
16
|
from .payment_method import PaymentMethod
|
|
17
|
+
from .recurring_invoice import RecurringInvoice, RecurringInvoiceRun
|
|
10
18
|
from .recurring_payment import RecurringPayment
|
|
11
19
|
from .recurring_payment_method import RecurringPaymentMethod
|
|
12
20
|
from .tokenize_result import TokenizeResult
|
|
@@ -28,4 +36,13 @@ __all__ = [
|
|
|
28
36
|
'TokenizeResult',
|
|
29
37
|
'MessageResult',
|
|
30
38
|
'FormLink',
|
|
39
|
+
'Invoice',
|
|
40
|
+
'InvoiceCustomer',
|
|
41
|
+
'InvoiceEvent',
|
|
42
|
+
'InvoiceItem',
|
|
43
|
+
'InvoiceLink',
|
|
44
|
+
'InvoicePayment',
|
|
45
|
+
'LineItem',
|
|
46
|
+
'RecurringInvoice',
|
|
47
|
+
'RecurringInvoiceRun',
|
|
31
48
|
]
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
from .invoice_customer import InvoiceCustomer
|
|
6
|
+
from .invoice_event import InvoiceEvent
|
|
7
|
+
from .invoice_payment import InvoicePayment
|
|
8
|
+
from .line_item import LineItem
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Invoice:
|
|
13
|
+
"""
|
|
14
|
+
A full invoice as returned by the show, create, update and action endpoints.
|
|
15
|
+
|
|
16
|
+
Money fields arrive as dollar amounts and are kept as strings, consistent
|
|
17
|
+
with the rest of the SDK, to avoid float rounding.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
id: int | None = None
|
|
21
|
+
token: str | None = None
|
|
22
|
+
invoice_number: str | None = None
|
|
23
|
+
status: str | None = None
|
|
24
|
+
payment_terms: str | None = None
|
|
25
|
+
issue_date: str | None = None
|
|
26
|
+
due_date: str | None = None
|
|
27
|
+
is_overdue: bool = False
|
|
28
|
+
subtotal: str | None = None
|
|
29
|
+
total: str | None = None
|
|
30
|
+
amount_paid: str | None = None
|
|
31
|
+
balance: str | None = None
|
|
32
|
+
allow_partial_payment: bool = False
|
|
33
|
+
thank_you_note: str | None = None
|
|
34
|
+
public_url: str | None = None
|
|
35
|
+
customer: InvoiceCustomer = field(default_factory=InvoiceCustomer)
|
|
36
|
+
items: list[LineItem] = field(default_factory=list)
|
|
37
|
+
payments: list[InvoicePayment] = field(default_factory=list)
|
|
38
|
+
events: list[InvoiceEvent] = field(default_factory=list)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_dict(cls, data: dict[str, Any]) -> 'Invoice':
|
|
42
|
+
return cls(
|
|
43
|
+
id=arr_int(data, 'id'),
|
|
44
|
+
token=arr_string(data, 'token'),
|
|
45
|
+
invoice_number=arr_string(data, 'invoice_number'),
|
|
46
|
+
status=arr_string(data, 'status'),
|
|
47
|
+
payment_terms=arr_string(data, 'payment_terms'),
|
|
48
|
+
issue_date=arr_string(data, 'issue_date'),
|
|
49
|
+
due_date=arr_string(data, 'due_date'),
|
|
50
|
+
is_overdue=arr_bool(data, 'is_overdue'),
|
|
51
|
+
subtotal=arr_string(data, 'subtotal'),
|
|
52
|
+
total=arr_string(data, 'total'),
|
|
53
|
+
amount_paid=arr_string(data, 'amount_paid'),
|
|
54
|
+
balance=arr_string(data, 'balance'),
|
|
55
|
+
allow_partial_payment=arr_bool(data, 'allow_partial_payment'),
|
|
56
|
+
thank_you_note=arr_string(data, 'thank_you_note'),
|
|
57
|
+
public_url=arr_string(data, 'public_url'),
|
|
58
|
+
customer=InvoiceCustomer.from_dict(arr_object(data, 'customer')),
|
|
59
|
+
items=[LineItem.from_dict(i) for i in arr_array(data, 'items') if isinstance(i, dict)],
|
|
60
|
+
payments=[InvoicePayment.from_dict(p) for p in arr_array(data, 'payments') if isinstance(p, dict)],
|
|
61
|
+
events=[InvoiceEvent.from_dict(e) for e in arr_array(data, 'events') if isinstance(e, dict)],
|
|
62
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
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 InvoiceCustomer:
|
|
9
|
+
"""The customer snapshot embedded in an invoice response."""
|
|
10
|
+
|
|
11
|
+
id: int | None = None
|
|
12
|
+
name: str | None = None
|
|
13
|
+
email: str | None = None
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_dict(cls, data: dict[str, Any]) -> 'InvoiceCustomer':
|
|
17
|
+
return cls(
|
|
18
|
+
id=arr_int(data, 'id'),
|
|
19
|
+
name=arr_string(data, 'name'),
|
|
20
|
+
email=arr_string(data, 'email'),
|
|
21
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from ..support.arr import arr_string
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class InvoiceEvent:
|
|
9
|
+
"""An entry in an invoice's history (created, sent, paid, voided, ...)."""
|
|
10
|
+
|
|
11
|
+
type: str | None = None
|
|
12
|
+
label: str | None = None
|
|
13
|
+
description: str | None = None
|
|
14
|
+
created_at: str | None = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_dict(cls, data: dict[str, Any]) -> 'InvoiceEvent':
|
|
18
|
+
return cls(
|
|
19
|
+
type=arr_string(data, 'type'),
|
|
20
|
+
label=arr_string(data, 'label'),
|
|
21
|
+
description=arr_string(data, 'description'),
|
|
22
|
+
created_at=arr_string(data, 'created_at'),
|
|
23
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
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 InvoiceItem:
|
|
9
|
+
"""
|
|
10
|
+
A Merchant item (fund or designation) that invoice line items reference by
|
|
11
|
+
``item_id``. This is the catalog entry, not a line on an invoice — for that,
|
|
12
|
+
see :class:`LineItem`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
id: int | None = None
|
|
16
|
+
name: str | None = None
|
|
17
|
+
description: str | None = None
|
|
18
|
+
price: str | None = None
|
|
19
|
+
tax_deductible: bool = False
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_dict(cls, data: dict[str, Any]) -> 'InvoiceItem':
|
|
23
|
+
return cls(
|
|
24
|
+
id=arr_int(data, 'id'),
|
|
25
|
+
name=arr_string(data, 'name'),
|
|
26
|
+
description=arr_string(data, 'description'),
|
|
27
|
+
price=arr_string(data, 'price'),
|
|
28
|
+
tax_deductible=arr_bool(data, 'tax_deductible'),
|
|
29
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from ..support.arr import arr_string
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class InvoiceLink:
|
|
9
|
+
"""The public payment link for an invoice."""
|
|
10
|
+
|
|
11
|
+
public_url: str | None = None
|
|
12
|
+
token: str | None = None
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def from_dict(cls, data: dict[str, Any]) -> 'InvoiceLink':
|
|
16
|
+
return cls(
|
|
17
|
+
public_url=arr_string(data, 'public_url'),
|
|
18
|
+
token=arr_string(data, 'token'),
|
|
19
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
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 InvoicePayment:
|
|
9
|
+
"""A payment recorded against an invoice."""
|
|
10
|
+
|
|
11
|
+
amount: str | None = None
|
|
12
|
+
paid_at: str | None = None
|
|
13
|
+
method: str | None = None
|
|
14
|
+
transaction_id: int | None = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_dict(cls, data: dict[str, Any]) -> 'InvoicePayment':
|
|
18
|
+
return cls(
|
|
19
|
+
amount=arr_string(data, 'amount'),
|
|
20
|
+
paid_at=arr_string(data, 'paid_at'),
|
|
21
|
+
method=arr_string(data, 'method'),
|
|
22
|
+
transaction_id=arr_int(data, 'transaction_id'),
|
|
23
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
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 LineItem:
|
|
9
|
+
"""
|
|
10
|
+
A single line on an invoice. ``item_id`` points at the Merchant item the line
|
|
11
|
+
was sourced from; ``name`` and ``unit_price`` are snapshotted at creation so
|
|
12
|
+
later edits to the item do not rewrite history.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
id: int | None = None
|
|
16
|
+
item_id: int | None = None
|
|
17
|
+
name: str | None = None
|
|
18
|
+
description: str | None = None
|
|
19
|
+
quantity: str | None = None
|
|
20
|
+
unit_price: str | None = None
|
|
21
|
+
amount: str | None = None
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_dict(cls, data: dict[str, Any]) -> 'LineItem':
|
|
25
|
+
return cls(
|
|
26
|
+
id=arr_int(data, 'id'),
|
|
27
|
+
item_id=arr_int(data, 'item_id'),
|
|
28
|
+
name=arr_string(data, 'name'),
|
|
29
|
+
description=arr_string(data, 'description'),
|
|
30
|
+
quantity=arr_string(data, 'quantity'),
|
|
31
|
+
unit_price=arr_string(data, 'unit_price'),
|
|
32
|
+
amount=arr_string(data, 'amount'),
|
|
33
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
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 .invoice_customer import InvoiceCustomer
|
|
6
|
+
from .line_item import LineItem
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class RecurringInvoiceRun:
|
|
11
|
+
"""A summary of one invoice already emitted by a recurring template."""
|
|
12
|
+
|
|
13
|
+
id: int | None = None
|
|
14
|
+
invoice_number: str | None = None
|
|
15
|
+
status: str | None = None
|
|
16
|
+
total: str | None = None
|
|
17
|
+
issue_date: str | None = None
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_dict(cls, data: dict[str, Any]) -> 'RecurringInvoiceRun':
|
|
21
|
+
return cls(
|
|
22
|
+
id=arr_int(data, 'id'),
|
|
23
|
+
invoice_number=arr_string(data, 'invoice_number'),
|
|
24
|
+
status=arr_string(data, 'status'),
|
|
25
|
+
total=arr_string(data, 'total'),
|
|
26
|
+
issue_date=arr_string(data, 'issue_date'),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class RecurringInvoice:
|
|
32
|
+
"""
|
|
33
|
+
A recurring-invoice template. ``recurrence_schedule`` is one of Weekly,
|
|
34
|
+
Biweekly, FirstFifteenth, Monthly or Yearly.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
id: int | None = None
|
|
38
|
+
status: str | None = None
|
|
39
|
+
recurrence_schedule: str | None = None
|
|
40
|
+
payment_terms: str | None = None
|
|
41
|
+
start_date: str | None = None
|
|
42
|
+
end_date: str | None = None
|
|
43
|
+
next_run_date: str | None = None
|
|
44
|
+
last_run_date: str | None = None
|
|
45
|
+
thank_you_note: str | None = None
|
|
46
|
+
customer: InvoiceCustomer = field(default_factory=InvoiceCustomer)
|
|
47
|
+
items: list[LineItem] = field(default_factory=list)
|
|
48
|
+
upcoming_run_dates: list[str] = field(default_factory=list)
|
|
49
|
+
invoices: list[RecurringInvoiceRun] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_dict(cls, data: dict[str, Any]) -> 'RecurringInvoice':
|
|
53
|
+
return cls(
|
|
54
|
+
id=arr_int(data, 'id'),
|
|
55
|
+
status=arr_string(data, 'status'),
|
|
56
|
+
recurrence_schedule=arr_string(data, 'recurrence_schedule'),
|
|
57
|
+
payment_terms=arr_string(data, 'payment_terms'),
|
|
58
|
+
start_date=arr_string(data, 'start_date'),
|
|
59
|
+
end_date=arr_string(data, 'end_date'),
|
|
60
|
+
next_run_date=arr_string(data, 'next_run_date'),
|
|
61
|
+
last_run_date=arr_string(data, 'last_run_date'),
|
|
62
|
+
thank_you_note=arr_string(data, 'thank_you_note'),
|
|
63
|
+
customer=InvoiceCustomer.from_dict(arr_object(data, 'customer')),
|
|
64
|
+
items=[LineItem.from_dict(i) for i in arr_array(data, 'items') if isinstance(i, dict)],
|
|
65
|
+
upcoming_run_dates=[str(d) for d in arr_array(data, 'upcoming_run_dates')],
|
|
66
|
+
invoices=[RecurringInvoiceRun.from_dict(i) for i in arr_array(data, 'invoices') if isinstance(i, dict)],
|
|
67
|
+
)
|
dime_payments/http/transport.py
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.invoice import Invoice
|
|
6
|
+
from ..data_objects.invoice_item import InvoiceItem
|
|
7
|
+
from ..data_objects.invoice_link import InvoiceLink
|
|
8
|
+
from ..data_objects.message_result import MessageResult
|
|
9
|
+
from ..pagination.cursor_page import CursorPage
|
|
10
|
+
from .abstract_resource import AbstractResource
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Invoices(AbstractResource):
|
|
14
|
+
"""
|
|
15
|
+
Invoice endpoints.
|
|
16
|
+
|
|
17
|
+
Every method takes the merchant ``sid`` explicitly; remaining fields go in an
|
|
18
|
+
``attributes`` dict. Identify the customer with ``customer_uuid`` — the same
|
|
19
|
+
identifier the customer, payment-method and address endpoints use, and the
|
|
20
|
+
only one :class:`Customer` exposes. ``customer_id`` is still accepted for
|
|
21
|
+
integrations written against the original contract; supply exactly one.
|
|
22
|
+
|
|
23
|
+
Line-item and invoice mutations return the refreshed invoice, so the caller
|
|
24
|
+
always sees recalculated totals rather than a detached fragment.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def list(self, sid: str, filters: dict[str, Any] | None = None) -> CursorPage[Invoice]:
|
|
28
|
+
body = self._envelope({'sid': sid}, filters or {})
|
|
29
|
+
return self._paginate('GET', 'invoices', body, Invoice.from_dict)
|
|
30
|
+
|
|
31
|
+
def show(self, sid: str, invoice_id: int | str) -> Invoice:
|
|
32
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
33
|
+
raw = self._transport.request('GET', 'invoice', body)
|
|
34
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
35
|
+
|
|
36
|
+
def create(self, sid: str, attributes: dict[str, Any]) -> Invoice:
|
|
37
|
+
"""
|
|
38
|
+
Create a draft invoice. Requires ``customer_uuid`` (or ``customer_id``),
|
|
39
|
+
``customer_name``, ``customer_email``, ``payment_terms``
|
|
40
|
+
(``due_on_receipt`` | ``net_15`` | ``net_30`` | ``net_60``) and at least
|
|
41
|
+
one entry in ``lines``, each referencing a Merchant ``item_id``.
|
|
42
|
+
"""
|
|
43
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
44
|
+
raw = self._transport.request('POST', 'invoice/create', body)
|
|
45
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
46
|
+
|
|
47
|
+
def update(self, sid: str, invoice_id: int | str, attributes: dict[str, Any]) -> Invoice:
|
|
48
|
+
"""Update a draft invoice. Passing ``lines`` replaces the existing lines."""
|
|
49
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id} | attributes)
|
|
50
|
+
raw = self._transport.request('PATCH', 'invoice/update', body)
|
|
51
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
52
|
+
|
|
53
|
+
def delete(self, sid: str, invoice_id: int | str) -> MessageResult:
|
|
54
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
55
|
+
raw = self._transport.request('POST', 'invoice/delete', body)
|
|
56
|
+
return MessageResult.from_dict(raw.get('data') or raw)
|
|
57
|
+
|
|
58
|
+
def send(self, sid: str, invoice_id: int | str) -> Invoice:
|
|
59
|
+
"""Email the invoice and advance it to Sent."""
|
|
60
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
61
|
+
raw = self._transport.request('POST', 'invoice/send', body)
|
|
62
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
63
|
+
|
|
64
|
+
def mark_sent(self, sid: str, invoice_id: int | str) -> Invoice:
|
|
65
|
+
"""Activate a draft for payment without emailing it."""
|
|
66
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
67
|
+
raw = self._transport.request('POST', 'invoice/mark-sent', body)
|
|
68
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
69
|
+
|
|
70
|
+
def void(self, sid: str, invoice_id: int | str) -> Invoice:
|
|
71
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
72
|
+
raw = self._transport.request('PATCH', 'invoice/void', body)
|
|
73
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
74
|
+
|
|
75
|
+
def duplicate(self, sid: str, invoice_id: int | str) -> Invoice:
|
|
76
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
77
|
+
raw = self._transport.request('POST', 'invoice/duplicate', body)
|
|
78
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
79
|
+
|
|
80
|
+
def pay(self, sid: str, invoice_id: int | str, attributes: dict[str, Any]) -> Invoice:
|
|
81
|
+
"""
|
|
82
|
+
Record a merchant-initiated (MOTO) payment against an open invoice.
|
|
83
|
+
|
|
84
|
+
``payment_type`` is required and is either ``cc`` or ``ach``. For a card,
|
|
85
|
+
pass a stored ``token`` or raw ``cardholder_name`` / ``card_number`` /
|
|
86
|
+
``expiration_date``; for ACH, pass ``routing_number`` /
|
|
87
|
+
``account_number`` / ``account_type`` / ``account_name``. Omit ``amount``
|
|
88
|
+
to pay the full balance.
|
|
89
|
+
"""
|
|
90
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id} | attributes)
|
|
91
|
+
raw = self._transport.request('POST', 'invoice/pay', body)
|
|
92
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
93
|
+
|
|
94
|
+
def get_link(self, sid: str, invoice_id: int | str) -> InvoiceLink:
|
|
95
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id})
|
|
96
|
+
raw = self._transport.request('GET', 'invoice/link', body)
|
|
97
|
+
return InvoiceLink.from_dict(raw.get('data') or {})
|
|
98
|
+
|
|
99
|
+
def list_items(self, sid: str) -> list[InvoiceItem]:
|
|
100
|
+
"""
|
|
101
|
+
List the Merchant's items (funds/designations) that line items can
|
|
102
|
+
reference. Scoped to the merchant, not to a single invoice — an
|
|
103
|
+
invoice's own lines are on :attr:`Invoice.items`.
|
|
104
|
+
"""
|
|
105
|
+
body = self._envelope({'sid': sid})
|
|
106
|
+
raw = self._transport.request('GET', 'invoice/items', body)
|
|
107
|
+
items = raw.get('data', [])
|
|
108
|
+
if isinstance(items, dict):
|
|
109
|
+
items = list(items.values())
|
|
110
|
+
return [InvoiceItem.from_dict(item) for item in items if isinstance(item, dict)]
|
|
111
|
+
|
|
112
|
+
def create_item(self, sid: str, attributes: dict[str, Any]) -> InvoiceItem:
|
|
113
|
+
"""
|
|
114
|
+
Create an invoicing-only Merchant item. ``name`` is required;
|
|
115
|
+
``description``, ``price`` and ``tax_deductible`` are optional.
|
|
116
|
+
"""
|
|
117
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
118
|
+
raw = self._transport.request('POST', 'invoice/item/create', body)
|
|
119
|
+
return InvoiceItem.from_dict(raw.get('data') or {})
|
|
120
|
+
|
|
121
|
+
def add_line_item(self, sid: str, invoice_id: int | str, attributes: dict[str, Any]) -> Invoice:
|
|
122
|
+
body = self._envelope({'sid': sid, 'invoice_id': invoice_id} | attributes)
|
|
123
|
+
raw = self._transport.request('POST', 'invoice/line-item/add', body)
|
|
124
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
125
|
+
|
|
126
|
+
def update_line_item(
|
|
127
|
+
self,
|
|
128
|
+
sid: str,
|
|
129
|
+
invoice_id: int | str,
|
|
130
|
+
line_item_id: int | str,
|
|
131
|
+
attributes: dict[str, Any],
|
|
132
|
+
) -> Invoice:
|
|
133
|
+
body = self._envelope(
|
|
134
|
+
{'sid': sid, 'invoice_id': invoice_id, 'line_item_id': line_item_id} | attributes
|
|
135
|
+
)
|
|
136
|
+
raw = self._transport.request('PATCH', 'invoice/line-item/update', body)
|
|
137
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
138
|
+
|
|
139
|
+
def delete_line_item(self, sid: str, invoice_id: int | str, line_item_id: int | str) -> Invoice:
|
|
140
|
+
body = self._envelope(
|
|
141
|
+
{'sid': sid, 'invoice_id': invoice_id, 'line_item_id': line_item_id}
|
|
142
|
+
)
|
|
143
|
+
raw = self._transport.request('POST', 'invoice/line-item/delete', body)
|
|
144
|
+
return Invoice.from_dict(raw.get('data') or {})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..data_objects.recurring_invoice import RecurringInvoice
|
|
6
|
+
from ..pagination.cursor_page import CursorPage
|
|
7
|
+
from .abstract_resource import AbstractResource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RecurringInvoices(AbstractResource):
|
|
11
|
+
def list(self, sid: str, filters: dict[str, Any] | None = None) -> CursorPage[RecurringInvoice]:
|
|
12
|
+
body = self._envelope({'sid': sid}, filters or {})
|
|
13
|
+
return self._paginate('GET', 'recurring-invoices', body, RecurringInvoice.from_dict)
|
|
14
|
+
|
|
15
|
+
def show(self, sid: str, recurring_invoice_id: int | str) -> RecurringInvoice:
|
|
16
|
+
body = self._envelope({'sid': sid, 'recurring_invoice_id': recurring_invoice_id})
|
|
17
|
+
raw = self._transport.request('GET', 'recurring-invoice', body)
|
|
18
|
+
return RecurringInvoice.from_dict(raw.get('data') or {})
|
|
19
|
+
|
|
20
|
+
def create(self, sid: str, attributes: dict[str, Any]) -> RecurringInvoice:
|
|
21
|
+
body = self._envelope({'sid': sid} | attributes)
|
|
22
|
+
raw = self._transport.request('POST', 'recurring-invoice/create', body)
|
|
23
|
+
return RecurringInvoice.from_dict(raw.get('data') or {})
|
|
24
|
+
|
|
25
|
+
def cancel(self, sid: str, recurring_invoice_id: int | str) -> RecurringInvoice:
|
|
26
|
+
body = self._envelope({'sid': sid, 'recurring_invoice_id': recurring_invoice_id})
|
|
27
|
+
raw = self._transport.request('POST', 'recurring-invoice/cancel', body)
|
|
28
|
+
return RecurringInvoice.from_dict(raw.get('data') or {})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dime-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Python client for the Dime Payments API
|
|
5
5
|
Author: Dime Technology
|
|
6
6
|
License: MIT License
|
|
@@ -118,6 +118,8 @@ returned as strings to avoid float rounding.
|
|
|
118
118
|
| `dime.addresses` | list, show, create, update, delete |
|
|
119
119
|
| `dime.deposits` | list, list_with_transactions, show |
|
|
120
120
|
| `dime.recurring_payments` | list, show, create, edit, pause, cancel, activate, delete |
|
|
121
|
+
| `dime.invoices` | list, show, create, update, delete, send, mark_sent, void, duplicate, pay, get_link, list_items, add_line_item, update_line_item, delete_line_item, create_item |
|
|
122
|
+
| `dime.recurring_invoices` | list, show, create, cancel |
|
|
121
123
|
|
|
122
124
|
### Transactions
|
|
123
125
|
|
|
@@ -208,6 +210,83 @@ dime.recurring_payments.activate('000010', rp.id)
|
|
|
208
210
|
dime.recurring_payments.cancel('000010', rp.id)
|
|
209
211
|
```
|
|
210
212
|
|
|
213
|
+
### Invoices
|
|
214
|
+
|
|
215
|
+
Invoices are scoped to a merchant `sid` and built from line items that each reference a merchant
|
|
216
|
+
item (a fund or designation). Draft invoices can be edited; once sent they are locked.
|
|
217
|
+
|
|
218
|
+
Identify the customer with `customer_uuid` — the same uuid every other resource uses, and the only
|
|
219
|
+
identifier the customer endpoints return. `customer_id` is still accepted for older integrations.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
# Look up (or create) the merchant items a line can reference
|
|
223
|
+
items = dime.invoices.list_items('000010')
|
|
224
|
+
item = dime.invoices.create_item('000010', {
|
|
225
|
+
'name': 'Consulting',
|
|
226
|
+
'description': 'Professional services',
|
|
227
|
+
'price': 125,
|
|
228
|
+
'tax_deductible': False,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
# Create a draft invoice with one or more line items
|
|
232
|
+
invoice = dime.invoices.create('000010', {
|
|
233
|
+
'customer_uuid': customer.uuid,
|
|
234
|
+
'customer_name': 'Jane Doe',
|
|
235
|
+
'customer_email': 'jane@example.com',
|
|
236
|
+
'payment_terms': 'net_15', # due_on_receipt | net_15 | net_30 | net_60
|
|
237
|
+
'lines': [
|
|
238
|
+
{'item_id': item.id, 'name': 'Consulting', 'description': '2 hours',
|
|
239
|
+
'quantity': 2, 'unit_price': 125},
|
|
240
|
+
],
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
# Line-item edits return the refreshed invoice, with totals recalculated
|
|
244
|
+
invoice = dime.invoices.add_line_item('000010', invoice.id, {
|
|
245
|
+
'item_id': item.id, 'name': 'Setup', 'quantity': 1, 'unit_price': 50,
|
|
246
|
+
})
|
|
247
|
+
invoice = dime.invoices.update_line_item('000010', invoice.id, invoice.items[0].id, {'quantity': 3})
|
|
248
|
+
invoice = dime.invoices.delete_line_item('000010', invoice.id, invoice.items[0].id)
|
|
249
|
+
|
|
250
|
+
# Email it to the customer, or activate the pay link without emailing
|
|
251
|
+
dime.invoices.send('000010', invoice.id)
|
|
252
|
+
dime.invoices.mark_sent('000010', invoice.id)
|
|
253
|
+
|
|
254
|
+
# Share the public pay link
|
|
255
|
+
link = dime.invoices.get_link('000010', invoice.id)
|
|
256
|
+
print(link.public_url)
|
|
257
|
+
|
|
258
|
+
# Take a merchant-initiated payment. payment_type is required; omit amount to
|
|
259
|
+
# pay the full balance.
|
|
260
|
+
dime.invoices.pay('000010', invoice.id, {
|
|
261
|
+
'payment_type': 'cc', # cc | ach
|
|
262
|
+
'token': pm.token,
|
|
263
|
+
'amount': 125,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
dime.invoices.void('000010', invoice.id)
|
|
267
|
+
dime.invoices.duplicate('000010', invoice.id)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Recurring invoices
|
|
271
|
+
|
|
272
|
+
Templates that emit an invoice on a schedule.
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
ri = dime.recurring_invoices.create('000010', {
|
|
276
|
+
'customer_uuid': customer.uuid,
|
|
277
|
+
'payment_terms': 'net_30',
|
|
278
|
+
'recurring_frequency': 'Monthly', # Weekly | Biweekly | FirstFifteenth | Monthly | Yearly
|
|
279
|
+
'recurring_start_date': '2026-09-01',
|
|
280
|
+
'lines': [
|
|
281
|
+
{'item_id': item.id, 'name': 'Retainer', 'quantity': 1, 'unit_price': 500},
|
|
282
|
+
],
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
print(ri.next_run_date, ri.upcoming_run_dates)
|
|
286
|
+
|
|
287
|
+
dime.recurring_invoices.cancel('000010', ri.id)
|
|
288
|
+
```
|
|
289
|
+
|
|
211
290
|
## Pagination
|
|
212
291
|
|
|
213
292
|
List endpoints return a `CursorPage`. Iterate one page, walk pages manually, or stream every
|
|
@@ -271,7 +350,9 @@ except DimeException as e:
|
|
|
271
350
|
## Notes
|
|
272
351
|
|
|
273
352
|
- **GET requests carry a JSON body.** The Dime API expects read parameters in the request body
|
|
274
|
-
even for `GET` endpoints; the SDK handles this transparently.
|
|
353
|
+
even for `GET` endpoints; the SDK handles this transparently. Point `base_url` at an `https://`
|
|
354
|
+
origin — an `http://` URL that 301-redirects to `https` will have its request body dropped by the
|
|
355
|
+
redirect, which surfaces as a `403` "You do not have access to this company." from the API.
|
|
275
356
|
- **No API versioning.** Endpoints live under `/api` with no version prefix.
|
|
276
357
|
|
|
277
358
|
## Development
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
dime_payments/__init__.py,sha256=nJGtKtehbZMxar3wgdk8RIG_nNPSb6G9rmMyC2PnjqM,638
|
|
2
|
-
dime_payments/client.py,sha256=
|
|
3
|
-
dime_payments/config.py,sha256=
|
|
4
|
-
dime_payments/data_objects/__init__.py,sha256=
|
|
2
|
+
dime_payments/client.py,sha256=dX_mH2q_hBZrC2s8B3Y3EYy3AWAvUYgTDNf1M0i9aFM,1328
|
|
3
|
+
dime_payments/config.py,sha256=uRmAy2Pon8Uz1CzVPQp47Gk1VQl5aRsQRXf-FA9xSPY,847
|
|
4
|
+
dime_payments/data_objects/__init__.py,sha256=fuavK4aZ-IFM_PyMpJ8fIZlip7-V5m5LOHTCr2XnZng,1409
|
|
5
5
|
dime_payments/data_objects/address.py,sha256=ocnxRM3JDLr4mDtlnVgsnUb6Hy5u6vNQlWVRFHzuMIw,950
|
|
6
6
|
dime_payments/data_objects/customer.py,sha256=fSUPB9PynX_R3Jntnjr130ExeZtgrmRZSVfds54rmV0,1141
|
|
7
7
|
dime_payments/data_objects/deposit.py,sha256=b0rQdNgdprMtP0pdMrepsWG3jBt7fJ4FGZ1EuHTRQhA,1139
|
|
8
8
|
dime_payments/data_objects/deposit_group.py,sha256=kmfwnDO0YgmvmlSvIakb4cMFYOMfYDDnXMR4eiabSKA,813
|
|
9
9
|
dime_payments/data_objects/deposit_with_transactions.py,sha256=HjmWB5v2R67z5xgqoZt1rlV5nxvixrTmySS-zzUFA0c,1420
|
|
10
10
|
dime_payments/data_objects/form_link.py,sha256=rPU7cJynsrfb_1QjT8DkZ8ShvXVXUScBqJNZVASeJCI,280
|
|
11
|
+
dime_payments/data_objects/invoice.py,sha256=x2_UplLFnmj2_BwTI2tgzflQJbCXdCk9jmmAoOlrWxg,2622
|
|
12
|
+
dime_payments/data_objects/invoice_customer.py,sha256=GtnomaFO8xlpRuElMPPAAtlT5DwJLyp3NB5wGqdW9Bc,528
|
|
13
|
+
dime_payments/data_objects/invoice_event.py,sha256=dvKOfcB9sxE6ehFQyB_lsfpA6iMFX3oK5WAUjSIpgDk,646
|
|
14
|
+
dime_payments/data_objects/invoice_item.py,sha256=aVgO0UM7vqJB2VjvtYMBpLsCWXKHus0r7pA9I7UHwxo,856
|
|
15
|
+
dime_payments/data_objects/invoice_link.py,sha256=kiWSXkl4jtsCZ-ODsEaFU12mTk3soe0qZ7ICDJ2MiEE,452
|
|
16
|
+
dime_payments/data_objects/invoice_payment.py,sha256=jDllzFpRdaWAa0s89iNPdQG0dy61S47hvEMIbnKEpv8,635
|
|
17
|
+
dime_payments/data_objects/line_item.py,sha256=EJtpxnSOoCOlZJcrq8jDarieICPHtQK-g6jipTDMDAg,1024
|
|
11
18
|
dime_payments/data_objects/merchant.py,sha256=U6RbP3QJACjOtrSExyodfz9zOzeU2hThXwGD6M0XATI,1900
|
|
12
19
|
dime_payments/data_objects/message_result.py,sha256=2gZYbokwPRdlBUz_vGgYzrJpxMnjMuzwtTdjXhUv1GM,299
|
|
13
20
|
dime_payments/data_objects/payment_method.py,sha256=-3kcHBGoXD12mzHocMoKU0aBwoNJ_9GJFx5eldXUJxA,2460
|
|
21
|
+
dime_payments/data_objects/recurring_invoice.py,sha256=r5Qf656ByedP0LhmoEg9KVsIpC_svaPGEu231Mqqiy8,2612
|
|
14
22
|
dime_payments/data_objects/recurring_payment.py,sha256=E6F3oX6aqWeE2Ek4ePqDDbZ79APdIKKXVip4bTcFU-g,2209
|
|
15
23
|
dime_payments/data_objects/recurring_payment_method.py,sha256=P22ITvvxAUJ4NaEc7MH2ItCFm6wk1mEuxRs4ffSUDrg,402
|
|
16
24
|
dime_payments/data_objects/tokenize_result.py,sha256=i27Hc0A-dFM8cX8s9A71yM95kd48ih25fvnb3SEml54,295
|
|
@@ -28,7 +36,7 @@ dime_payments/exceptions/server_exception.py,sha256=SCUubaM1wTwFMW3E9mBSxry4A-0Q
|
|
|
28
36
|
dime_payments/exceptions/validation_exception.py,sha256=_HGMPN-5iFq6RWJY-06Oaak565MeD-yRf6GnbXp7Sog,689
|
|
29
37
|
dime_payments/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
38
|
dime_payments/http/error_handler.py,sha256=dqClsyDIcCPS6CtiqtGvEwTnORj24m2muXThXfHdhX0,2873
|
|
31
|
-
dime_payments/http/transport.py,sha256=
|
|
39
|
+
dime_payments/http/transport.py,sha256=xYJ1OWHDTuTGuZgZPCLsV75AXEHUDyH1enBoIwwurac,2642
|
|
32
40
|
dime_payments/pagination/__init__.py,sha256=J2k8htn29Nu92KQh4wtEeN0StyMc8cWxVY8jQydF-dQ,62
|
|
33
41
|
dime_payments/pagination/cursor_page.py,sha256=Xpbt33ceBy_HQlqe3wFbxA93bUfx3rHcqXfhVAkOQJ0,2263
|
|
34
42
|
dime_payments/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,13 +44,15 @@ dime_payments/resources/abstract_resource.py,sha256=-XmnqJvk2tVNe44JeY_TTAzD4UiU
|
|
|
36
44
|
dime_payments/resources/addresses.py,sha256=sg0q3YCnkdGlGM0onbBh_UsatrIu4VyzAA1BdZ1QRZ0,1713
|
|
37
45
|
dime_payments/resources/customers.py,sha256=6_9gG9tc2uHN-onhzNFP0NO67NxGOYqbwZmF96vU4bo,1545
|
|
38
46
|
dime_payments/resources/deposits.py,sha256=k8wE7kT_baHjXmXxzN9v6_2BqDb0GSUlINPRVtjmyUI,1213
|
|
47
|
+
dime_payments/resources/invoices.py,sha256=_n2MPmBgn4IvQ9nhq9q8gSuVrxDDjfs5251S9PpywwA,7062
|
|
39
48
|
dime_payments/resources/merchants.py,sha256=blVx7JIAps9ktdpLjjQofEnVkK_KgXOQP8beBPkHrkg,1370
|
|
40
49
|
dime_payments/resources/payment_methods.py,sha256=KJWryjnZadyohG1r6pmULjwPIjPIjScSK7ekQYamvYA,1775
|
|
50
|
+
dime_payments/resources/recurring_invoices.py,sha256=mn-I-0o_uac6w-8HiwIWS2y0ptCZpVLZm3F60cyhl8w,1431
|
|
41
51
|
dime_payments/resources/recurring_payments.py,sha256=DPihjc-C7oLLfPrP5UveNpEKHmUxayv4k3hFpguJ35c,2940
|
|
42
52
|
dime_payments/resources/transactions.py,sha256=Q0VwYDicdGuM1WQXqxPX_KS313A0KZVCeuVCyKS_G7k,2742
|
|
43
53
|
dime_payments/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
54
|
dime_payments/support/arr.py,sha256=_mboi_q3HGn_Sx_MgU2exkbl-fnP2gdDY8yeSC8hkNc,1722
|
|
45
|
-
dime_python_sdk-1.
|
|
46
|
-
dime_python_sdk-1.
|
|
47
|
-
dime_python_sdk-1.
|
|
48
|
-
dime_python_sdk-1.
|
|
55
|
+
dime_python_sdk-1.2.0.dist-info/METADATA,sha256=RwVXCQEwsDl3sm2uRGyIORwqFTAIP-o_6lQMuDVsGhk,12723
|
|
56
|
+
dime_python_sdk-1.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
57
|
+
dime_python_sdk-1.2.0.dist-info/licenses/LICENSE,sha256=-Xc5dLgfuFLVQLsSKNYmJKtNZCUU1_9ErffdGiwH-DU,1072
|
|
58
|
+
dime_python_sdk-1.2.0.dist-info/RECORD,,
|
|
File without changes
|