dime-python-sdk 1.3.0__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.0 → dime_python_sdk-1.4.0}/PKG-INFO +141 -4
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/README.md +139 -2
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/pyproject.toml +1 -1
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/client.py +10 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/config.py +1 -1
- {dime_python_sdk-1.3.0 → 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.0 → dime_python_sdk-1.4.0}/src/dime_payments/http/error_handler.py +18 -5
- {dime_python_sdk-1.3.0 → 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.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/invoices.py +8 -1
- {dime_python_sdk-1.3.0 → 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.0 → 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.0 → 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.0 → 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.0 → dime_python_sdk-1.4.0}/tests/unit/test_transactions.py +38 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/.github/workflows/publish.yml +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/.gitignore +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/LICENSE +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/address.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/cover_fee_quote.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/customer.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/deposit.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/deposit_group.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/deposit_with_transactions.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/form_link.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_customer.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_event.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_item.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_link.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/invoice_payment.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/line_item.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/merchant.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/message_result.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/payment_method.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/recurring_invoice.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/recurring_payment.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/recurring_payment_method.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/tokenize_result.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/transaction.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/data_objects/transaction_address.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/api_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/authentication_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/connection_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/dime_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/not_found_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/permission_denied_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/rate_limit_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/server_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/exceptions/validation_exception.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/http/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/pagination/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/pagination/cursor_page.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/abstract_resource.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/addresses.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/customers.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/deposits.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/payment_methods.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/recurring_invoices.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/resources/recurring_payments.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/support/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/src/dime_payments/support/arr.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/helpers.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/__init__.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_addresses.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_customers.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_deposits.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_invoices.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_pagination.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_payment_methods.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_recurring_invoices.py +0 -0
- {dime_python_sdk-1.3.0 → dime_python_sdk-1.4.0}/tests/unit/test_recurring_payments.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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
|
|
@@ -218,6 +252,11 @@ item (a fund or designation). Draft invoices can be edited; once sent they are l
|
|
|
218
252
|
Identify the customer with `customer_uuid` — the same uuid every other resource uses, and the only
|
|
219
253
|
identifier the customer endpoints return. `customer_id` is still accepted for older integrations.
|
|
220
254
|
|
|
255
|
+
**Statuses.** `invoice.status` is one of `draft`, `sent`, `viewed`, `partially_paid`, `paid`, `void` or
|
|
256
|
+
`refunded`. `paid` is not always final: if the customer's bank returns an ACH payment, the invoice is
|
|
257
|
+
reopened (back to `partially_paid`, `viewed` or `sent`, with `amount_paid` and `balance` updated) and an
|
|
258
|
+
`invoice_payment_returned` webhook fires. Re-read the invoice rather than caching a `paid` status forever.
|
|
259
|
+
|
|
221
260
|
```python
|
|
222
261
|
# Look up (or create) the merchant items a line can reference
|
|
223
262
|
items = dime.invoices.list_items('000010')
|
|
@@ -331,6 +370,101 @@ print(ri.next_run_date, ri.upcoming_run_dates)
|
|
|
331
370
|
dime.recurring_invoices.cancel('000010', ri.id)
|
|
332
371
|
```
|
|
333
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
|
+
|
|
334
468
|
## Pagination
|
|
335
469
|
|
|
336
470
|
List endpoints return a `CursorPage`. Iterate one page, walk pages manually, or stream every
|
|
@@ -391,6 +525,9 @@ except DimeException as e:
|
|
|
391
525
|
| `ConnectionException` | No HTTP response (DNS, timeout, network error) |
|
|
392
526
|
| `ApiException` | Any other non-2xx |
|
|
393
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
|
+
|
|
394
531
|
## Notes
|
|
395
532
|
|
|
396
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
|
|
@@ -174,6 +208,11 @@ item (a fund or designation). Draft invoices can be edited; once sent they are l
|
|
|
174
208
|
Identify the customer with `customer_uuid` — the same uuid every other resource uses, and the only
|
|
175
209
|
identifier the customer endpoints return. `customer_id` is still accepted for older integrations.
|
|
176
210
|
|
|
211
|
+
**Statuses.** `invoice.status` is one of `draft`, `sent`, `viewed`, `partially_paid`, `paid`, `void` or
|
|
212
|
+
`refunded`. `paid` is not always final: if the customer's bank returns an ACH payment, the invoice is
|
|
213
|
+
reopened (back to `partially_paid`, `viewed` or `sent`, with `amount_paid` and `balance` updated) and an
|
|
214
|
+
`invoice_payment_returned` webhook fires. Re-read the invoice rather than caching a `paid` status forever.
|
|
215
|
+
|
|
177
216
|
```python
|
|
178
217
|
# Look up (or create) the merchant items a line can reference
|
|
179
218
|
items = dime.invoices.list_items('000010')
|
|
@@ -287,6 +326,101 @@ print(ri.next_run_date, ri.upcoming_run_dates)
|
|
|
287
326
|
dime.recurring_invoices.cancel('000010', ri.id)
|
|
288
327
|
```
|
|
289
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
|
+
|
|
290
424
|
## Pagination
|
|
291
425
|
|
|
292
426
|
List endpoints return a `CursorPage`. Iterate one page, walk pages manually, or stream every
|
|
@@ -347,6 +481,9 @@ except DimeException as e:
|
|
|
347
481
|
| `ConnectionException` | No HTTP response (DNS, timeout, network error) |
|
|
348
482
|
| `ApiException` | Any other non-2xx |
|
|
349
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
|
+
|
|
350
487
|
## Notes
|
|
351
488
|
|
|
352
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
|
+
)
|