dime-python-sdk 1.3.1__tar.gz → 1.4.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.
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/PKG-INFO +135 -3
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/README.md +134 -2
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/pyproject.toml +1 -1
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/client.py +10 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/config.py +1 -1
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/__init__.py +27 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/application_status.py +38 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/chargeback.py +63 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/document.py +49 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/document_upload_result.py +42 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/fund_release.py +63 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/held_balance.py +50 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/releasable_transactions.py +57 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/subscribe_result.py +29 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/subscription.py +61 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/subscription_item.py +29 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/subscription_payment_method.py +31 -0
- dime_python_sdk-1.4.0/src/dime_payments/data_objects/subscription_plan.py +46 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/http/error_handler.py +18 -5
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/http/transport.py +40 -2
- dime_python_sdk-1.4.0/src/dime_payments/resources/chargebacks.py +34 -0
- dime_python_sdk-1.4.0/src/dime_payments/resources/documents.py +81 -0
- dime_python_sdk-1.4.0/src/dime_payments/resources/funds.py +77 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/invoices.py +2 -1
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/merchants.py +10 -0
- dime_python_sdk-1.4.0/src/dime_payments/resources/subscription_plans.py +97 -0
- dime_python_sdk-1.4.0/src/dime_payments/resources/subscriptions.py +53 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/transactions.py +31 -0
- dime_python_sdk-1.4.0/tests/unit/test_chargebacks.py +79 -0
- dime_python_sdk-1.4.0/tests/unit/test_documents.py +144 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_error_handling.py +21 -0
- dime_python_sdk-1.4.0/tests/unit/test_funds.py +172 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_merchants.py +33 -0
- dime_python_sdk-1.4.0/tests/unit/test_subscription_plans.py +140 -0
- dime_python_sdk-1.4.0/tests/unit/test_subscriptions.py +101 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_transactions.py +38 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/.github/workflows/publish.yml +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/.gitignore +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/LICENSE +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/address.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/cover_fee_quote.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/customer.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/deposit.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/deposit_group.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/deposit_with_transactions.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/form_link.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_customer.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_event.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_item.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_link.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_payment.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/line_item.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/merchant.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/message_result.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/payment_method.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/recurring_invoice.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/recurring_payment.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/recurring_payment_method.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/tokenize_result.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/transaction.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/transaction_address.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/api_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/authentication_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/connection_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/dime_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/not_found_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/permission_denied_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/rate_limit_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/server_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/validation_exception.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/http/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/pagination/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/pagination/cursor_page.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/abstract_resource.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/addresses.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/customers.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/deposits.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/payment_methods.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/recurring_invoices.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/resources/recurring_payments.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/support/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/src/dime_payments/support/arr.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/helpers.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/__init__.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_addresses.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_customers.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_deposits.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_invoices.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_pagination.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_payment_methods.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_recurring_invoices.py +0 -0
- {dime_python_sdk-1.3.1 → dime_python_sdk-1.4.0}/tests/unit/test_recurring_payments.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dime-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Python client for the Dime Payments API
|
|
5
5
|
Author: Dime Technology
|
|
6
6
|
License: MIT License
|
|
@@ -111,15 +111,20 @@ returned as strings to avoid float rounding.
|
|
|
111
111
|
|
|
112
112
|
| Property | Endpoints |
|
|
113
113
|
| ------------------------------- | --------------------------------------------------------------------- |
|
|
114
|
-
| `dime.transactions` | charge_card, charge_ach, tokenize_card, refund, void, show, list
|
|
114
|
+
| `dime.transactions` | charge_card, charge_card_token, charge_ach, authorize, capture, tokenize_card, refund, void, show, list |
|
|
115
115
|
| `dime.customers` | list, show, create, update, delete |
|
|
116
116
|
| `dime.payment_methods` | list, show, create, update, delete |
|
|
117
|
-
| `dime.merchants` | list, show, create, update, get_form_link
|
|
117
|
+
| `dime.merchants` | list, show, create, update, get_form_link, application_status |
|
|
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
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
122
|
| `dime.recurring_invoices` | list, show, create, cancel |
|
|
123
|
+
| `dime.chargebacks` | list, show |
|
|
124
|
+
| `dime.documents` | upload, list |
|
|
125
|
+
| `dime.funds` | balance, transactions, release |
|
|
126
|
+
| `dime.subscription_plans` | list, show, create, edit, delete, publish, archive, unarchive, subscribe |
|
|
127
|
+
| `dime.subscriptions` | list, show, pause, resume, cancel |
|
|
123
128
|
|
|
124
129
|
### Transactions
|
|
125
130
|
|
|
@@ -165,6 +170,35 @@ dime.transactions.void('000010', 'CC', 123456)
|
|
|
165
170
|
txn = dime.transactions.show('000010', {'transaction_info_id': 123456})
|
|
166
171
|
```
|
|
167
172
|
|
|
173
|
+
#### Authorize now, capture later
|
|
174
|
+
|
|
175
|
+
`authorize()` holds the amount on the card without moving money. Pass its `transaction_number` to
|
|
176
|
+
`capture()` to collect, or to `void()` to release the hold. Capture promptly — the issuer drops an
|
|
177
|
+
uncaptured hold on its own schedule — and only once: a partial capture settles that amount and
|
|
178
|
+
releases the rest. Needs the `transaction:authorize-capture` ability.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
auth = dime.transactions.authorize('000010', {'amount': '100.00', 'token': 'tok_abc123'})
|
|
182
|
+
|
|
183
|
+
# Collect it: omit the amount to capture the full hold
|
|
184
|
+
dime.transactions.capture('000010', auth.transaction_number, '80.00')
|
|
185
|
+
|
|
186
|
+
# ...or release the hold instead
|
|
187
|
+
dime.transactions.void('000010', 'CC', auth.transaction_number)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Merchant onboarding
|
|
191
|
+
|
|
192
|
+
Follow up an application sent with `get_form_link()`. `status` is the headline; while it is
|
|
193
|
+
`underwriting`, read `application_status` — only `needs_documents` asks you to act. `boarded` says
|
|
194
|
+
whether the merchant can take money. The `application_status_changed` webhook carries the same
|
|
195
|
+
fields, so poll only to reconcile.
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
status = dime.merchants.application_status('000010')
|
|
199
|
+
print(status.status, status.application_status, status.boarded)
|
|
200
|
+
```
|
|
201
|
+
|
|
168
202
|
### Customers, payment methods, addresses
|
|
169
203
|
|
|
170
204
|
```python
|
|
@@ -336,6 +370,101 @@ print(ri.next_run_date, ri.upcoming_run_dates)
|
|
|
336
370
|
dime.recurring_invoices.cancel('000010', ri.id)
|
|
337
371
|
```
|
|
338
372
|
|
|
373
|
+
### Chargebacks and documents
|
|
374
|
+
|
|
375
|
+
Chargebacks arrive from the processor once a day; the chargeback webhooks tell you when one opens or
|
|
376
|
+
changes, and these endpoints let you reconcile. Contest one by uploading evidence as a
|
|
377
|
+
`RetrievalRequest` document against its `transaction_info_id`.
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
for cb in dime.chargebacks.list('000010', {'representment_status': 'New'}).auto_paging():
|
|
381
|
+
print(cb.transaction_info_id, cb.chargeback_amount, cb.representment_date)
|
|
382
|
+
|
|
383
|
+
cb = dime.chargebacks.show('000010', '8675309')
|
|
384
|
+
|
|
385
|
+
# Each file is a path or a (filename, bytes-or-file-object) pair. Up to 10 per call,
|
|
386
|
+
# 9 MB each, as PDF, JPG, PNG, DOC, DOCX or RTF.
|
|
387
|
+
result = dime.documents.upload(
|
|
388
|
+
'000010',
|
|
389
|
+
'RetrievalRequest', # Verification | FraudHolds | Underwriting | RetrievalRequest
|
|
390
|
+
['receipt.pdf', ('signature.png', png_bytes)],
|
|
391
|
+
chargeback_transaction_info_id=cb.transaction_info_id,
|
|
392
|
+
)
|
|
393
|
+
for failure in result.failed: # files are stored independently; re-send only these
|
|
394
|
+
print(failure.file_name, failure.reason)
|
|
395
|
+
|
|
396
|
+
docs = dime.documents.list('000010', {'chargeback_transaction_info_id': cb.transaction_info_id})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`upload()` is the one `multipart/form-data` request in the API; the SDK builds it for you. Uploading
|
|
400
|
+
does not forward anything to the processor — Dime reviews the documents and sends them on.
|
|
401
|
+
|
|
402
|
+
### Held funds
|
|
403
|
+
|
|
404
|
+
For merchants on a tier that holds their balance rather than sweeping it to the bank. Any other
|
|
405
|
+
merchant gets a `ValidationException` (422). Reading needs `funds:read`; releasing needs
|
|
406
|
+
`funds:release` on an affiliate key.
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
balance = dime.funds.balance('000010')
|
|
410
|
+
print(balance.available, balance.releasable) # release against releasable
|
|
411
|
+
|
|
412
|
+
# Release by amount...
|
|
413
|
+
result = dime.funds.release('000010', 'payout-2026-10-06-0001', amount='1500.00')
|
|
414
|
+
|
|
415
|
+
# ...or by payment, all or nothing
|
|
416
|
+
payable = dime.funds.transactions('000010')
|
|
417
|
+
result = dime.funds.release(
|
|
418
|
+
'000010',
|
|
419
|
+
'payout-2026-10-06-0002',
|
|
420
|
+
transaction_info_ids=[t.transaction_info_id for t in payable.transactions],
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
print(result.release.status) # released | failed | unknown
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Use a fresh `idempotency_key` for every intended release and reuse it when retrying: a repeat
|
|
427
|
+
returns the original release (`result.replayed`) instead of sending money twice. `unknown` means no
|
|
428
|
+
confirmation came back and it may have gone through; its amount stays out of `releasable` until it
|
|
429
|
+
is reconciled, so a later release cannot pay it twice. A release the processor declines is returned
|
|
430
|
+
with status `failed` and a `failure_reason`; nothing moved. A request refused before anything was
|
|
431
|
+
recorded raises: `ValidationException` (422) for more than is releasable or an ineligible payment,
|
|
432
|
+
`ApiException` (409) for a reused key or a release already in flight, and `ServerException` (503)
|
|
433
|
+
when the processor cannot be reached.
|
|
434
|
+
|
|
435
|
+
### Subscription plans and subscriptions
|
|
436
|
+
|
|
437
|
+
A plan is a recurring offering built from merchant items. Create it as a draft, publish it, then
|
|
438
|
+
subscribe customers to it; each subscriber keeps their own snapshot, so later edits do not change
|
|
439
|
+
what they pay.
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
plan = dime.subscription_plans.create('000010', {
|
|
443
|
+
'name': 'Monthly Membership',
|
|
444
|
+
'recurrence_schedule': 'Monthly', # Weekly | Biweekly | FirstFifteenth | Monthly | Yearly
|
|
445
|
+
'lines': [
|
|
446
|
+
{'item_id': item.id, 'name': 'Base membership', 'quantity': 1, 'unit_price': 25},
|
|
447
|
+
],
|
|
448
|
+
})
|
|
449
|
+
dime.subscription_plans.publish('000010', plan.id)
|
|
450
|
+
|
|
451
|
+
# Charges the first payment now; a decline raises ValidationException
|
|
452
|
+
result = dime.subscription_plans.subscribe('000010', plan.id, customer.uuid, pm.id)
|
|
453
|
+
|
|
454
|
+
sub = dime.subscriptions.show('000010', result.subscription_id)
|
|
455
|
+
dime.subscriptions.pause('000010', sub.id, '2026-12-01') # omit the date to pause indefinitely
|
|
456
|
+
dime.subscriptions.resume('000010', sub.id)
|
|
457
|
+
dime.subscriptions.cancel('000010', sub.id)
|
|
458
|
+
|
|
459
|
+
# edit() replaces the plan wholesale, so send every field and line
|
|
460
|
+
dime.subscription_plans.edit('000010', plan.id, {
|
|
461
|
+
'name': 'Monthly Membership',
|
|
462
|
+
'recurrence_schedule': 'Monthly',
|
|
463
|
+
'lines': [{'item_id': item.id, 'name': 'Base membership', 'quantity': 1, 'unit_price': 30}],
|
|
464
|
+
})
|
|
465
|
+
dime.subscription_plans.archive('000010', plan.id) # stop new sign-ups; unarchive() to reopen as a draft
|
|
466
|
+
```
|
|
467
|
+
|
|
339
468
|
## Pagination
|
|
340
469
|
|
|
341
470
|
List endpoints return a `CursorPage`. Iterate one page, walk pages manually, or stream every
|
|
@@ -396,6 +525,9 @@ except DimeException as e:
|
|
|
396
525
|
| `ConnectionException` | No HTTP response (DNS, timeout, network error) |
|
|
397
526
|
| `ApiException` | Any other non-2xx |
|
|
398
527
|
|
|
528
|
+
The exception message is the API's own where it sends one. Some list endpoints answer `404` when
|
|
529
|
+
nothing matches: an empty chargeback, document or subscription list raises `NotFoundException`.
|
|
530
|
+
|
|
399
531
|
## Notes
|
|
400
532
|
|
|
401
533
|
- **GET requests carry a JSON body.** The Dime API expects read parameters in the request body
|
|
@@ -67,15 +67,20 @@ returned as strings to avoid float rounding.
|
|
|
67
67
|
|
|
68
68
|
| Property | Endpoints |
|
|
69
69
|
| ------------------------------- | --------------------------------------------------------------------- |
|
|
70
|
-
| `dime.transactions` | charge_card, charge_ach, tokenize_card, refund, void, show, list
|
|
70
|
+
| `dime.transactions` | charge_card, charge_card_token, charge_ach, authorize, capture, tokenize_card, refund, void, show, list |
|
|
71
71
|
| `dime.customers` | list, show, create, update, delete |
|
|
72
72
|
| `dime.payment_methods` | list, show, create, update, delete |
|
|
73
|
-
| `dime.merchants` | list, show, create, update, get_form_link
|
|
73
|
+
| `dime.merchants` | list, show, create, update, get_form_link, application_status |
|
|
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
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
78
|
| `dime.recurring_invoices` | list, show, create, cancel |
|
|
79
|
+
| `dime.chargebacks` | list, show |
|
|
80
|
+
| `dime.documents` | upload, list |
|
|
81
|
+
| `dime.funds` | balance, transactions, release |
|
|
82
|
+
| `dime.subscription_plans` | list, show, create, edit, delete, publish, archive, unarchive, subscribe |
|
|
83
|
+
| `dime.subscriptions` | list, show, pause, resume, cancel |
|
|
79
84
|
|
|
80
85
|
### Transactions
|
|
81
86
|
|
|
@@ -121,6 +126,35 @@ dime.transactions.void('000010', 'CC', 123456)
|
|
|
121
126
|
txn = dime.transactions.show('000010', {'transaction_info_id': 123456})
|
|
122
127
|
```
|
|
123
128
|
|
|
129
|
+
#### Authorize now, capture later
|
|
130
|
+
|
|
131
|
+
`authorize()` holds the amount on the card without moving money. Pass its `transaction_number` to
|
|
132
|
+
`capture()` to collect, or to `void()` to release the hold. Capture promptly — the issuer drops an
|
|
133
|
+
uncaptured hold on its own schedule — and only once: a partial capture settles that amount and
|
|
134
|
+
releases the rest. Needs the `transaction:authorize-capture` ability.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
auth = dime.transactions.authorize('000010', {'amount': '100.00', 'token': 'tok_abc123'})
|
|
138
|
+
|
|
139
|
+
# Collect it: omit the amount to capture the full hold
|
|
140
|
+
dime.transactions.capture('000010', auth.transaction_number, '80.00')
|
|
141
|
+
|
|
142
|
+
# ...or release the hold instead
|
|
143
|
+
dime.transactions.void('000010', 'CC', auth.transaction_number)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Merchant onboarding
|
|
147
|
+
|
|
148
|
+
Follow up an application sent with `get_form_link()`. `status` is the headline; while it is
|
|
149
|
+
`underwriting`, read `application_status` — only `needs_documents` asks you to act. `boarded` says
|
|
150
|
+
whether the merchant can take money. The `application_status_changed` webhook carries the same
|
|
151
|
+
fields, so poll only to reconcile.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
status = dime.merchants.application_status('000010')
|
|
155
|
+
print(status.status, status.application_status, status.boarded)
|
|
156
|
+
```
|
|
157
|
+
|
|
124
158
|
### Customers, payment methods, addresses
|
|
125
159
|
|
|
126
160
|
```python
|
|
@@ -292,6 +326,101 @@ print(ri.next_run_date, ri.upcoming_run_dates)
|
|
|
292
326
|
dime.recurring_invoices.cancel('000010', ri.id)
|
|
293
327
|
```
|
|
294
328
|
|
|
329
|
+
### Chargebacks and documents
|
|
330
|
+
|
|
331
|
+
Chargebacks arrive from the processor once a day; the chargeback webhooks tell you when one opens or
|
|
332
|
+
changes, and these endpoints let you reconcile. Contest one by uploading evidence as a
|
|
333
|
+
`RetrievalRequest` document against its `transaction_info_id`.
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
for cb in dime.chargebacks.list('000010', {'representment_status': 'New'}).auto_paging():
|
|
337
|
+
print(cb.transaction_info_id, cb.chargeback_amount, cb.representment_date)
|
|
338
|
+
|
|
339
|
+
cb = dime.chargebacks.show('000010', '8675309')
|
|
340
|
+
|
|
341
|
+
# Each file is a path or a (filename, bytes-or-file-object) pair. Up to 10 per call,
|
|
342
|
+
# 9 MB each, as PDF, JPG, PNG, DOC, DOCX or RTF.
|
|
343
|
+
result = dime.documents.upload(
|
|
344
|
+
'000010',
|
|
345
|
+
'RetrievalRequest', # Verification | FraudHolds | Underwriting | RetrievalRequest
|
|
346
|
+
['receipt.pdf', ('signature.png', png_bytes)],
|
|
347
|
+
chargeback_transaction_info_id=cb.transaction_info_id,
|
|
348
|
+
)
|
|
349
|
+
for failure in result.failed: # files are stored independently; re-send only these
|
|
350
|
+
print(failure.file_name, failure.reason)
|
|
351
|
+
|
|
352
|
+
docs = dime.documents.list('000010', {'chargeback_transaction_info_id': cb.transaction_info_id})
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`upload()` is the one `multipart/form-data` request in the API; the SDK builds it for you. Uploading
|
|
356
|
+
does not forward anything to the processor — Dime reviews the documents and sends them on.
|
|
357
|
+
|
|
358
|
+
### Held funds
|
|
359
|
+
|
|
360
|
+
For merchants on a tier that holds their balance rather than sweeping it to the bank. Any other
|
|
361
|
+
merchant gets a `ValidationException` (422). Reading needs `funds:read`; releasing needs
|
|
362
|
+
`funds:release` on an affiliate key.
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
balance = dime.funds.balance('000010')
|
|
366
|
+
print(balance.available, balance.releasable) # release against releasable
|
|
367
|
+
|
|
368
|
+
# Release by amount...
|
|
369
|
+
result = dime.funds.release('000010', 'payout-2026-10-06-0001', amount='1500.00')
|
|
370
|
+
|
|
371
|
+
# ...or by payment, all or nothing
|
|
372
|
+
payable = dime.funds.transactions('000010')
|
|
373
|
+
result = dime.funds.release(
|
|
374
|
+
'000010',
|
|
375
|
+
'payout-2026-10-06-0002',
|
|
376
|
+
transaction_info_ids=[t.transaction_info_id for t in payable.transactions],
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
print(result.release.status) # released | failed | unknown
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Use a fresh `idempotency_key` for every intended release and reuse it when retrying: a repeat
|
|
383
|
+
returns the original release (`result.replayed`) instead of sending money twice. `unknown` means no
|
|
384
|
+
confirmation came back and it may have gone through; its amount stays out of `releasable` until it
|
|
385
|
+
is reconciled, so a later release cannot pay it twice. A release the processor declines is returned
|
|
386
|
+
with status `failed` and a `failure_reason`; nothing moved. A request refused before anything was
|
|
387
|
+
recorded raises: `ValidationException` (422) for more than is releasable or an ineligible payment,
|
|
388
|
+
`ApiException` (409) for a reused key or a release already in flight, and `ServerException` (503)
|
|
389
|
+
when the processor cannot be reached.
|
|
390
|
+
|
|
391
|
+
### Subscription plans and subscriptions
|
|
392
|
+
|
|
393
|
+
A plan is a recurring offering built from merchant items. Create it as a draft, publish it, then
|
|
394
|
+
subscribe customers to it; each subscriber keeps their own snapshot, so later edits do not change
|
|
395
|
+
what they pay.
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
plan = dime.subscription_plans.create('000010', {
|
|
399
|
+
'name': 'Monthly Membership',
|
|
400
|
+
'recurrence_schedule': 'Monthly', # Weekly | Biweekly | FirstFifteenth | Monthly | Yearly
|
|
401
|
+
'lines': [
|
|
402
|
+
{'item_id': item.id, 'name': 'Base membership', 'quantity': 1, 'unit_price': 25},
|
|
403
|
+
],
|
|
404
|
+
})
|
|
405
|
+
dime.subscription_plans.publish('000010', plan.id)
|
|
406
|
+
|
|
407
|
+
# Charges the first payment now; a decline raises ValidationException
|
|
408
|
+
result = dime.subscription_plans.subscribe('000010', plan.id, customer.uuid, pm.id)
|
|
409
|
+
|
|
410
|
+
sub = dime.subscriptions.show('000010', result.subscription_id)
|
|
411
|
+
dime.subscriptions.pause('000010', sub.id, '2026-12-01') # omit the date to pause indefinitely
|
|
412
|
+
dime.subscriptions.resume('000010', sub.id)
|
|
413
|
+
dime.subscriptions.cancel('000010', sub.id)
|
|
414
|
+
|
|
415
|
+
# edit() replaces the plan wholesale, so send every field and line
|
|
416
|
+
dime.subscription_plans.edit('000010', plan.id, {
|
|
417
|
+
'name': 'Monthly Membership',
|
|
418
|
+
'recurrence_schedule': 'Monthly',
|
|
419
|
+
'lines': [{'item_id': item.id, 'name': 'Base membership', 'quantity': 1, 'unit_price': 30}],
|
|
420
|
+
})
|
|
421
|
+
dime.subscription_plans.archive('000010', plan.id) # stop new sign-ups; unarchive() to reopen as a draft
|
|
422
|
+
```
|
|
423
|
+
|
|
295
424
|
## Pagination
|
|
296
425
|
|
|
297
426
|
List endpoints return a `CursorPage`. Iterate one page, walk pages manually, or stream every
|
|
@@ -352,6 +481,9 @@ except DimeException as e:
|
|
|
352
481
|
| `ConnectionException` | No HTTP response (DNS, timeout, network error) |
|
|
353
482
|
| `ApiException` | Any other non-2xx |
|
|
354
483
|
|
|
484
|
+
The exception message is the API's own where it sends one. Some list endpoints answer `404` when
|
|
485
|
+
nothing matches: an empty chargeback, document or subscription list raises `NotFoundException`.
|
|
486
|
+
|
|
355
487
|
## Notes
|
|
356
488
|
|
|
357
489
|
- **GET requests carry a JSON body.** The Dime API expects read parameters in the request body
|
|
@@ -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
|
|
@@ -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
|
+
)
|