dime-python-sdk 1.0.0__tar.gz → 1.2.0__tar.gz

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 (74) hide show
  1. dime_python_sdk-1.2.0/.github/workflows/publish.yml +62 -0
  2. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/PKG-INFO +83 -2
  3. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/README.md +82 -1
  4. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/pyproject.toml +1 -1
  5. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/client.py +4 -0
  6. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/config.py +1 -1
  7. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/__init__.py +17 -0
  8. dime_python_sdk-1.2.0/src/dime_payments/data_objects/invoice.py +62 -0
  9. dime_python_sdk-1.2.0/src/dime_payments/data_objects/invoice_customer.py +21 -0
  10. dime_python_sdk-1.2.0/src/dime_payments/data_objects/invoice_event.py +23 -0
  11. dime_python_sdk-1.2.0/src/dime_payments/data_objects/invoice_item.py +29 -0
  12. dime_python_sdk-1.2.0/src/dime_payments/data_objects/invoice_link.py +19 -0
  13. dime_python_sdk-1.2.0/src/dime_payments/data_objects/invoice_payment.py +23 -0
  14. dime_python_sdk-1.2.0/src/dime_payments/data_objects/line_item.py +33 -0
  15. dime_python_sdk-1.2.0/src/dime_payments/data_objects/recurring_invoice.py +67 -0
  16. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/http/transport.py +1 -1
  17. dime_python_sdk-1.2.0/src/dime_payments/resources/invoices.py +144 -0
  18. dime_python_sdk-1.2.0/src/dime_payments/resources/recurring_invoices.py +28 -0
  19. dime_python_sdk-1.2.0/tests/unit/test_invoices.py +256 -0
  20. dime_python_sdk-1.2.0/tests/unit/test_recurring_invoices.py +83 -0
  21. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/.gitignore +0 -0
  22. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/LICENSE +0 -0
  23. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/__init__.py +0 -0
  24. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/address.py +0 -0
  25. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/customer.py +0 -0
  26. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/deposit.py +0 -0
  27. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/deposit_group.py +0 -0
  28. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/deposit_with_transactions.py +0 -0
  29. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/form_link.py +0 -0
  30. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/merchant.py +0 -0
  31. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/message_result.py +0 -0
  32. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/payment_method.py +0 -0
  33. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/recurring_payment.py +0 -0
  34. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/recurring_payment_method.py +0 -0
  35. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/tokenize_result.py +0 -0
  36. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/transaction.py +0 -0
  37. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/data_objects/transaction_address.py +0 -0
  38. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/__init__.py +0 -0
  39. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/api_exception.py +0 -0
  40. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/authentication_exception.py +0 -0
  41. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/connection_exception.py +0 -0
  42. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/dime_exception.py +0 -0
  43. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/not_found_exception.py +0 -0
  44. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/permission_denied_exception.py +0 -0
  45. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/rate_limit_exception.py +0 -0
  46. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/server_exception.py +0 -0
  47. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/exceptions/validation_exception.py +0 -0
  48. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/http/__init__.py +0 -0
  49. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/http/error_handler.py +0 -0
  50. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/pagination/__init__.py +0 -0
  51. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/pagination/cursor_page.py +0 -0
  52. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/__init__.py +0 -0
  53. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/abstract_resource.py +0 -0
  54. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/addresses.py +0 -0
  55. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/customers.py +0 -0
  56. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/deposits.py +0 -0
  57. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/merchants.py +0 -0
  58. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/payment_methods.py +0 -0
  59. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/recurring_payments.py +0 -0
  60. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/resources/transactions.py +0 -0
  61. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/support/__init__.py +0 -0
  62. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/src/dime_payments/support/arr.py +0 -0
  63. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/__init__.py +0 -0
  64. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/helpers.py +0 -0
  65. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/__init__.py +0 -0
  66. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_addresses.py +0 -0
  67. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_customers.py +0 -0
  68. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_deposits.py +0 -0
  69. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_error_handling.py +0 -0
  70. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_merchants.py +0 -0
  71. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_pagination.py +0 -0
  72. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_payment_methods.py +0 -0
  73. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_recurring_payments.py +0 -0
  74. {dime_python_sdk-1.0.0 → dime_python_sdk-1.2.0}/tests/unit/test_transactions.py +0 -0
@@ -0,0 +1,62 @@
1
+ name: Publish
2
+
3
+ # Publishing is driven by GitHub Releases: tag, write the release notes, hit
4
+ # publish, and this ships the tag to PyPI. workflow_dispatch is the escape hatch
5
+ # for republishing a tag whose release already exists — select the tag as the
6
+ # ref when dispatching.
7
+ on:
8
+ release:
9
+ types: [published]
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ publish:
17
+ name: Publish to PyPI
18
+ runs-on: ubuntu-latest
19
+ environment: pypi
20
+ permissions:
21
+ contents: read
22
+ id-token: write # required for PyPI trusted publishing
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: '3.12'
30
+
31
+ # The SDK carries its version in three places and they drift silently.
32
+ # v1.0.0 sat on PyPI for ten weeks while main had moved on, so fail loudly
33
+ # rather than publish a package that misreports itself in X-Dime-Sdk.
34
+ - name: Verify the tag matches every declared version
35
+ env:
36
+ TAG_NAME: ${{ github.event.release.tag_name || github.ref_name }}
37
+ run: |
38
+ tag="${TAG_NAME#v}"
39
+ proj=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
40
+ cfg=$(python -c "import re; print(re.search(r\"VERSION = '([^']+)'\", open('src/dime_payments/config.py').read()).group(1))")
41
+ trn=$(python -c "import re; print(re.search(r\"SDK_VERSION = '([^']+)'\", open('src/dime_payments/http/transport.py').read()).group(1))")
42
+ echo "tag=$tag pyproject.toml=$proj config.py=$cfg transport.py=$trn"
43
+ [ "$tag" = "$proj" ] || { echo "::error::pyproject.toml ($proj) does not match tag ($tag)"; exit 1; }
44
+ [ "$tag" = "$cfg" ] || { echo "::error::config.py ($cfg) does not match tag ($tag)"; exit 1; }
45
+ [ "$tag" = "$trn" ] || { echo "::error::transport.py ($trn) does not match tag ($tag)"; exit 1; }
46
+
47
+ - name: Install and test
48
+ run: |
49
+ python -m pip install --upgrade pip
50
+ pip install -e ".[dev]"
51
+ pytest
52
+
53
+ - name: Build distributions
54
+ run: |
55
+ pip install build
56
+ python -m build
57
+
58
+ # Trusted publishing — authenticates via OIDC, so there is no PyPI token
59
+ # to store or rotate. Requires a trusted publisher configured on PyPI for
60
+ # this repo, this workflow filename, and the "pypi" environment.
61
+ - name: Publish
62
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dime-python-sdk
3
- Version: 1.0.0
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
@@ -74,6 +74,8 @@ returned as strings to avoid float rounding.
74
74
  | `dime.addresses` | list, show, create, update, delete |
75
75
  | `dime.deposits` | list, list_with_transactions, show |
76
76
  | `dime.recurring_payments` | list, show, create, edit, pause, cancel, activate, delete |
77
+ | `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 |
78
+ | `dime.recurring_invoices` | list, show, create, cancel |
77
79
 
78
80
  ### Transactions
79
81
 
@@ -164,6 +166,83 @@ dime.recurring_payments.activate('000010', rp.id)
164
166
  dime.recurring_payments.cancel('000010', rp.id)
165
167
  ```
166
168
 
169
+ ### Invoices
170
+
171
+ Invoices are scoped to a merchant `sid` and built from line items that each reference a merchant
172
+ item (a fund or designation). Draft invoices can be edited; once sent they are locked.
173
+
174
+ Identify the customer with `customer_uuid` — the same uuid every other resource uses, and the only
175
+ identifier the customer endpoints return. `customer_id` is still accepted for older integrations.
176
+
177
+ ```python
178
+ # Look up (or create) the merchant items a line can reference
179
+ items = dime.invoices.list_items('000010')
180
+ item = dime.invoices.create_item('000010', {
181
+ 'name': 'Consulting',
182
+ 'description': 'Professional services',
183
+ 'price': 125,
184
+ 'tax_deductible': False,
185
+ })
186
+
187
+ # Create a draft invoice with one or more line items
188
+ invoice = dime.invoices.create('000010', {
189
+ 'customer_uuid': customer.uuid,
190
+ 'customer_name': 'Jane Doe',
191
+ 'customer_email': 'jane@example.com',
192
+ 'payment_terms': 'net_15', # due_on_receipt | net_15 | net_30 | net_60
193
+ 'lines': [
194
+ {'item_id': item.id, 'name': 'Consulting', 'description': '2 hours',
195
+ 'quantity': 2, 'unit_price': 125},
196
+ ],
197
+ })
198
+
199
+ # Line-item edits return the refreshed invoice, with totals recalculated
200
+ invoice = dime.invoices.add_line_item('000010', invoice.id, {
201
+ 'item_id': item.id, 'name': 'Setup', 'quantity': 1, 'unit_price': 50,
202
+ })
203
+ invoice = dime.invoices.update_line_item('000010', invoice.id, invoice.items[0].id, {'quantity': 3})
204
+ invoice = dime.invoices.delete_line_item('000010', invoice.id, invoice.items[0].id)
205
+
206
+ # Email it to the customer, or activate the pay link without emailing
207
+ dime.invoices.send('000010', invoice.id)
208
+ dime.invoices.mark_sent('000010', invoice.id)
209
+
210
+ # Share the public pay link
211
+ link = dime.invoices.get_link('000010', invoice.id)
212
+ print(link.public_url)
213
+
214
+ # Take a merchant-initiated payment. payment_type is required; omit amount to
215
+ # pay the full balance.
216
+ dime.invoices.pay('000010', invoice.id, {
217
+ 'payment_type': 'cc', # cc | ach
218
+ 'token': pm.token,
219
+ 'amount': 125,
220
+ })
221
+
222
+ dime.invoices.void('000010', invoice.id)
223
+ dime.invoices.duplicate('000010', invoice.id)
224
+ ```
225
+
226
+ ### Recurring invoices
227
+
228
+ Templates that emit an invoice on a schedule.
229
+
230
+ ```python
231
+ ri = dime.recurring_invoices.create('000010', {
232
+ 'customer_uuid': customer.uuid,
233
+ 'payment_terms': 'net_30',
234
+ 'recurring_frequency': 'Monthly', # Weekly | Biweekly | FirstFifteenth | Monthly | Yearly
235
+ 'recurring_start_date': '2026-09-01',
236
+ 'lines': [
237
+ {'item_id': item.id, 'name': 'Retainer', 'quantity': 1, 'unit_price': 500},
238
+ ],
239
+ })
240
+
241
+ print(ri.next_run_date, ri.upcoming_run_dates)
242
+
243
+ dime.recurring_invoices.cancel('000010', ri.id)
244
+ ```
245
+
167
246
  ## Pagination
168
247
 
169
248
  List endpoints return a `CursorPage`. Iterate one page, walk pages manually, or stream every
@@ -227,7 +306,9 @@ except DimeException as e:
227
306
  ## Notes
228
307
 
229
308
  - **GET requests carry a JSON body.** The Dime API expects read parameters in the request body
230
- even for `GET` endpoints; the SDK handles this transparently.
309
+ even for `GET` endpoints; the SDK handles this transparently. Point `base_url` at an `https://`
310
+ origin — an `http://` URL that 301-redirects to `https` will have its request body dropped by the
311
+ redirect, which surfaces as a `403` "You do not have access to this company." from the API.
231
312
  - **No API versioning.** Endpoints live under `/api` with no version prefix.
232
313
 
233
314
  ## Development
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dime-python-sdk"
7
- version = "1.0.0"
7
+ version = "1.2.0"
8
8
  description = "Python client for the Dime Payments API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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
@@ -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.0.0'
11
+ VERSION = '1.2.0'
12
12
 
13
13
  def __init__(
14
14
  self,
@@ -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
+ )
@@ -11,7 +11,7 @@ from .error_handler import ErrorHandler
11
11
  if TYPE_CHECKING:
12
12
  from ..config import Config
13
13
 
14
- SDK_VERSION = '1.0.0'
14
+ SDK_VERSION = '1.2.0'
15
15
 
16
16
 
17
17
  class Transport: