dime-python-sdk 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dime_payments/config.py CHANGED
@@ -8,7 +8,7 @@ if TYPE_CHECKING:
8
8
 
9
9
  class Config:
10
10
  DEFAULT_BASE_URL = 'https://app.dimepayments.com'
11
- VERSION = '1.2.0'
11
+ VERSION = '1.3.0'
12
12
 
13
13
  def __init__(
14
14
  self,
@@ -0,0 +1,48 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from ..support.arr import arr_object, arr_string
5
+
6
+
7
+ @dataclass
8
+ class CoverFeeQuote:
9
+ """
10
+ The processing fee a cover-fee invoice adds on top of what the customer pays,
11
+ quoted for both payment methods.
12
+
13
+ Present on an :class:`Invoice` only when ``cover_fee_required`` is true. The fee
14
+ is NOT a line item and is NOT part of the invoice's ``total``: the merchant is
15
+ owed ``total``, and the customer is charged ``total`` plus this fee. Card and
16
+ ACH rates differ, so the amount depends on how the customer chooses to pay —
17
+ ``cc_total`` is the higher of the two and what the invoice and its emails lead
18
+ with.
19
+
20
+ ``basis`` names what the quote was computed against — currently always
21
+ ``balance``, the amount still outstanding. Paying a partial amount re-quotes the
22
+ fee against that amount, so treat these as a quote for settling in full today
23
+ rather than a fixed charge.
24
+
25
+ Money is kept as strings, consistent with the rest of the SDK, to avoid float
26
+ rounding.
27
+ """
28
+
29
+ basis: str | None = None
30
+ base: str | None = None
31
+ cc_fee: str | None = None
32
+ cc_total: str | None = None
33
+ ach_fee: str | None = None
34
+ ach_total: str | None = None
35
+
36
+ @classmethod
37
+ def from_dict(cls, data: dict[str, Any]) -> 'CoverFeeQuote':
38
+ cc = arr_object(data, 'cc')
39
+ ach = arr_object(data, 'ach')
40
+
41
+ return cls(
42
+ basis=arr_string(data, 'basis'),
43
+ base=arr_string(data, 'base'),
44
+ cc_fee=arr_string(cc, 'fee'),
45
+ cc_total=arr_string(cc, 'total'),
46
+ ach_fee=arr_string(ach, 'fee'),
47
+ ach_total=arr_string(ach, 'total'),
48
+ )
@@ -2,6 +2,7 @@ from dataclasses import dataclass, field
2
2
  from typing import Any
3
3
 
4
4
  from ..support.arr import arr_array, arr_bool, arr_int, arr_object, arr_string
5
+ from .cover_fee_quote import CoverFeeQuote
5
6
  from .invoice_customer import InvoiceCustomer
6
7
  from .invoice_event import InvoiceEvent
7
8
  from .invoice_payment import InvoicePayment
@@ -15,6 +16,10 @@ class Invoice:
15
16
 
16
17
  Money fields arrive as dollar amounts and are kept as strings, consistent
17
18
  with the rest of the SDK, to avoid float rounding.
19
+
20
+ When ``cover_fee_required`` is set the customer must also pay the processing
21
+ fee, which is reported on :attr:`cover_fee_quote` rather than included in
22
+ ``total`` — so what settles is more than what the invoice says.
18
23
  """
19
24
 
20
25
  id: int | None = None
@@ -30,6 +35,8 @@ class Invoice:
30
35
  amount_paid: str | None = None
31
36
  balance: str | None = None
32
37
  allow_partial_payment: bool = False
38
+ cover_fee_required: bool = False
39
+ cover_fee_quote: CoverFeeQuote | None = None
33
40
  thank_you_note: str | None = None
34
41
  public_url: str | None = None
35
42
  customer: InvoiceCustomer = field(default_factory=InvoiceCustomer)
@@ -53,6 +60,12 @@ class Invoice:
53
60
  amount_paid=arr_string(data, 'amount_paid'),
54
61
  balance=arr_string(data, 'balance'),
55
62
  allow_partial_payment=arr_bool(data, 'allow_partial_payment'),
63
+ cover_fee_required=arr_bool(data, 'cover_fee_required'),
64
+ cover_fee_quote=(
65
+ CoverFeeQuote.from_dict(data['cover_fee_quote'])
66
+ if isinstance(data.get('cover_fee_quote'), dict)
67
+ else None
68
+ ),
56
69
  thank_you_note=arr_string(data, 'thank_you_note'),
57
70
  public_url=arr_string(data, 'public_url'),
58
71
  customer=InvoiceCustomer.from_dict(arr_object(data, 'customer')),
@@ -6,9 +6,17 @@ from ..support.arr import arr_int, arr_string
6
6
 
7
7
  @dataclass
8
8
  class InvoicePayment:
9
- """A payment recorded against an invoice."""
9
+ """
10
+ A payment recorded against an invoice.
11
+
12
+ ``amount`` is what was credited to the invoice; ``cover_fee`` is the processing
13
+ fee charged on top of it, so ``amount + cover_fee`` is what the customer
14
+ actually paid. It is zero unless the invoice required the customer to cover
15
+ fees.
16
+ """
10
17
 
11
18
  amount: str | None = None
19
+ cover_fee: str | None = None
12
20
  paid_at: str | None = None
13
21
  method: str | None = None
14
22
  transaction_id: int | None = None
@@ -17,6 +25,7 @@ class InvoicePayment:
17
25
  def from_dict(cls, data: dict[str, Any]) -> 'InvoicePayment':
18
26
  return cls(
19
27
  amount=arr_string(data, 'amount'),
28
+ cover_fee=arr_string(data, 'cover_fee'),
20
29
  paid_at=arr_string(data, 'paid_at'),
21
30
  method=arr_string(data, 'method'),
22
31
  transaction_id=arr_int(data, 'transaction_id'),
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Any
3
3
 
4
- from ..support.arr import arr_array, arr_int, arr_object, arr_string
4
+ from ..support.arr import arr_array, arr_bool, arr_int, arr_object, arr_string
5
5
  from .invoice_customer import InvoiceCustomer
6
6
  from .line_item import LineItem
7
7
 
@@ -38,6 +38,8 @@ class RecurringInvoice:
38
38
  status: str | None = None
39
39
  recurrence_schedule: str | None = None
40
40
  payment_terms: str | None = None
41
+ # Copied onto every invoice this template generates.
42
+ cover_fee_required: bool = False
41
43
  start_date: str | None = None
42
44
  end_date: str | None = None
43
45
  next_run_date: str | None = None
@@ -55,6 +57,7 @@ class RecurringInvoice:
55
57
  status=arr_string(data, 'status'),
56
58
  recurrence_schedule=arr_string(data, 'recurrence_schedule'),
57
59
  payment_terms=arr_string(data, 'payment_terms'),
60
+ cover_fee_required=arr_bool(data, 'cover_fee_required'),
58
61
  start_date=arr_string(data, 'start_date'),
59
62
  end_date=arr_string(data, 'end_date'),
60
63
  next_run_date=arr_string(data, 'next_run_date'),
@@ -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.2.0'
14
+ SDK_VERSION = '1.3.0'
15
15
 
16
16
 
17
17
  class Transport:
@@ -39,6 +39,12 @@ class Invoices(AbstractResource):
39
39
  ``customer_name``, ``customer_email``, ``payment_terms``
40
40
  (``due_on_receipt`` | ``net_15`` | ``net_30`` | ``net_60``) and at least
41
41
  one entry in ``lines``, each referencing a Merchant ``item_id``.
42
+
43
+ Pass ``cover_fee_required`` to make the customer pay the processing fee. The
44
+ fee is added on top of the invoice at payment time rather than becoming a
45
+ line item, so ``total`` stays the amount owed to the merchant — read
46
+ :attr:`Invoice.cover_fee_quote` for what the customer will actually be
47
+ charged. Omit it to inherit the Merchant's invoice setting.
42
48
  """
43
49
  body = self._envelope({'sid': sid} | attributes)
44
50
  raw = self._transport.request('POST', 'invoice/create', body)
@@ -86,6 +92,12 @@ class Invoices(AbstractResource):
86
92
  ``expiration_date``; for ACH, pass ``routing_number`` /
87
93
  ``account_number`` / ``account_type`` / ``account_name``. Omit ``amount``
88
94
  to pay the full balance.
95
+
96
+ On a cover-fee invoice the processing fee for ``payment_type`` is charged on
97
+ top of ``amount``, so the card or bank account is debited more than the
98
+ invoice is credited. The fee lands as ``cover_fee`` on the matching entry in
99
+ :attr:`Invoice.payments`. Card and ACH rates differ, so the same ``amount``
100
+ settles differently per ``payment_type``.
89
101
  """
90
102
  body = self._envelope({'sid': sid, 'invoice_id': invoice_id} | attributes)
91
103
  raw = self._transport.request('POST', 'invoice/pay', body)
@@ -18,6 +18,13 @@ class RecurringInvoices(AbstractResource):
18
18
  return RecurringInvoice.from_dict(raw.get('data') or {})
19
19
 
20
20
  def create(self, sid: str, attributes: dict[str, Any]) -> RecurringInvoice:
21
+ """
22
+ Create a recurring-invoice template. When ``recurring_start_date`` is today
23
+ the first invoice is generated and sent immediately.
24
+
25
+ ``cover_fee_required`` makes the customer cover the processing fee on every
26
+ invoice this template generates.
27
+ """
21
28
  body = self._envelope({'sid': sid} | attributes)
22
29
  raw = self._transport.request('POST', 'recurring-invoice/create', body)
23
30
  return RecurringInvoice.from_dict(raw.get('data') or {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dime-python-sdk
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Python client for the Dime Payments API
5
5
  Author: Dime Technology
6
6
  License: MIT License
@@ -267,9 +267,52 @@ dime.invoices.void('000010', invoice.id)
267
267
  dime.invoices.duplicate('000010', invoice.id)
268
268
  ```
269
269
 
270
+ #### Making the customer cover processing fees
271
+
272
+ Set `cover_fee_required` and the customer must pay the processing fee — it is not an optional
273
+ checkbox at checkout. The fee is **not** a line item and is **not** part of `total`: the merchant is
274
+ still owed `total`, and the fee is added on top of whatever the customer pays.
275
+
276
+ Card and ACH rates differ, so the charge depends on how the customer pays. `cover_fee_quote` gives
277
+ you both, quoted against the outstanding balance:
278
+
279
+ ```python
280
+ invoice = dime.invoices.create('000010', {
281
+ 'customer_uuid': customer.uuid,
282
+ 'customer_name': 'Jane Doe',
283
+ 'customer_email': 'jane@example.com',
284
+ 'payment_terms': 'net_15',
285
+ 'cover_fee_required': True, # omit to inherit the merchant's invoice setting
286
+ 'lines': [
287
+ {'item_id': item.id, 'name': 'Consulting', 'quantity': 1, 'unit_price': 100},
288
+ ],
289
+ })
290
+
291
+ invoice.total # '100.00' — what the merchant is owed
292
+ invoice.cover_fee_quote.cc_total # '104.32' — charged if they pay by card
293
+ invoice.cover_fee_quote.ach_total # '101.26' — charged if they pay by bank
294
+ ```
295
+
296
+ The card figure is the higher of the two and is what the invoice and its emails lead with. A partial
297
+ payment re-quotes the fee against the partial amount, so treat the quote as "settling in full today"
298
+ rather than a fixed charge. `cover_fee_quote` is `None` when no fee is required.
299
+
300
+ To reconcile a payment, `amount` was credited to the invoice and `cover_fee` was charged on top:
301
+
302
+ ```python
303
+ payment = invoice.payments[0]
304
+ payment.amount # '100.00' — applied to the balance
305
+ payment.cover_fee # '4.32' — the fee the customer also paid
306
+ # The customer was charged amount + cover_fee.
307
+ ```
308
+
309
+ `pay()` behaves the same way: the fee for the `payment_type` you pass is added to `amount`, so the
310
+ card or bank account is debited more than the invoice is credited.
311
+
270
312
  ### Recurring invoices
271
313
 
272
- Templates that emit an invoice on a schedule.
314
+ Templates that emit an invoice on a schedule. `cover_fee_required` is copied onto every invoice a
315
+ template generates.
273
316
 
274
317
  ```python
275
318
  ri = dime.recurring_invoices.create('000010', {
@@ -277,6 +320,7 @@ ri = dime.recurring_invoices.create('000010', {
277
320
  'payment_terms': 'net_30',
278
321
  'recurring_frequency': 'Monthly', # Weekly | Biweekly | FirstFifteenth | Monthly | Yearly
279
322
  'recurring_start_date': '2026-09-01',
323
+ 'cover_fee_required': True, # optional
280
324
  'lines': [
281
325
  {'item_id': item.id, 'name': 'Retainer', 'quantity': 1, 'unit_price': 500},
282
326
  ],
@@ -1,24 +1,25 @@
1
1
  dime_payments/__init__.py,sha256=nJGtKtehbZMxar3wgdk8RIG_nNPSb6G9rmMyC2PnjqM,638
2
2
  dime_payments/client.py,sha256=dX_mH2q_hBZrC2s8B3Y3EYy3AWAvUYgTDNf1M0i9aFM,1328
3
- dime_payments/config.py,sha256=uRmAy2Pon8Uz1CzVPQp47Gk1VQl5aRsQRXf-FA9xSPY,847
3
+ dime_payments/config.py,sha256=IEINY3s56MAaIDhVO5Cajx4D_lmUoR5SG8BLO3IOpM4,847
4
4
  dime_payments/data_objects/__init__.py,sha256=fuavK4aZ-IFM_PyMpJ8fIZlip7-V5m5LOHTCr2XnZng,1409
5
5
  dime_payments/data_objects/address.py,sha256=ocnxRM3JDLr4mDtlnVgsnUb6Hy5u6vNQlWVRFHzuMIw,950
6
+ dime_payments/data_objects/cover_fee_quote.py,sha256=KuS1GMVe-Stixate6crTswvvhnq8e9Jfq3v4C2pRYr0,1717
6
7
  dime_payments/data_objects/customer.py,sha256=fSUPB9PynX_R3Jntnjr130ExeZtgrmRZSVfds54rmV0,1141
7
8
  dime_payments/data_objects/deposit.py,sha256=b0rQdNgdprMtP0pdMrepsWG3jBt7fJ4FGZ1EuHTRQhA,1139
8
9
  dime_payments/data_objects/deposit_group.py,sha256=kmfwnDO0YgmvmlSvIakb4cMFYOMfYDDnXMR4eiabSKA,813
9
10
  dime_payments/data_objects/deposit_with_transactions.py,sha256=HjmWB5v2R67z5xgqoZt1rlV5nxvixrTmySS-zzUFA0c,1420
10
11
  dime_payments/data_objects/form_link.py,sha256=rPU7cJynsrfb_1QjT8DkZ8ShvXVXUScBqJNZVASeJCI,280
11
- dime_payments/data_objects/invoice.py,sha256=x2_UplLFnmj2_BwTI2tgzflQJbCXdCk9jmmAoOlrWxg,2622
12
+ dime_payments/data_objects/invoice.py,sha256=GQ6g-9e4toNsgJF-2WsnJ0S3mgeizrtYbBYhI3ur46E,3251
12
13
  dime_payments/data_objects/invoice_customer.py,sha256=GtnomaFO8xlpRuElMPPAAtlT5DwJLyp3NB5wGqdW9Bc,528
13
14
  dime_payments/data_objects/invoice_event.py,sha256=dvKOfcB9sxE6ehFQyB_lsfpA6iMFX3oK5WAUjSIpgDk,646
14
15
  dime_payments/data_objects/invoice_item.py,sha256=aVgO0UM7vqJB2VjvtYMBpLsCWXKHus0r7pA9I7UHwxo,856
15
16
  dime_payments/data_objects/invoice_link.py,sha256=kiWSXkl4jtsCZ-ODsEaFU12mTk3soe0qZ7ICDJ2MiEE,452
16
- dime_payments/data_objects/invoice_payment.py,sha256=jDllzFpRdaWAa0s89iNPdQG0dy61S47hvEMIbnKEpv8,635
17
+ dime_payments/data_objects/invoice_payment.py,sha256=EPvs8FhhDyxEyRo8RoiVSRZK5LIBEwF5Iwjcw7DM0Ac,983
17
18
  dime_payments/data_objects/line_item.py,sha256=EJtpxnSOoCOlZJcrq8jDarieICPHtQK-g6jipTDMDAg,1024
18
19
  dime_payments/data_objects/merchant.py,sha256=U6RbP3QJACjOtrSExyodfz9zOzeU2hThXwGD6M0XATI,1900
19
20
  dime_payments/data_objects/message_result.py,sha256=2gZYbokwPRdlBUz_vGgYzrJpxMnjMuzwtTdjXhUv1GM,299
20
21
  dime_payments/data_objects/payment_method.py,sha256=-3kcHBGoXD12mzHocMoKU0aBwoNJ_9GJFx5eldXUJxA,2460
21
- dime_payments/data_objects/recurring_invoice.py,sha256=r5Qf656ByedP0LhmoEg9KVsIpC_svaPGEu231Mqqiy8,2612
22
+ dime_payments/data_objects/recurring_invoice.py,sha256=pS4DEZdMnxhA6qwcM3bUH7k-yb0NU8-OvJdrk_cdihA,2785
22
23
  dime_payments/data_objects/recurring_payment.py,sha256=E6F3oX6aqWeE2Ek4ePqDDbZ79APdIKKXVip4bTcFU-g,2209
23
24
  dime_payments/data_objects/recurring_payment_method.py,sha256=P22ITvvxAUJ4NaEc7MH2ItCFm6wk1mEuxRs4ffSUDrg,402
24
25
  dime_payments/data_objects/tokenize_result.py,sha256=i27Hc0A-dFM8cX8s9A71yM95kd48ih25fvnb3SEml54,295
@@ -36,7 +37,7 @@ dime_payments/exceptions/server_exception.py,sha256=SCUubaM1wTwFMW3E9mBSxry4A-0Q
36
37
  dime_payments/exceptions/validation_exception.py,sha256=_HGMPN-5iFq6RWJY-06Oaak565MeD-yRf6GnbXp7Sog,689
37
38
  dime_payments/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
39
  dime_payments/http/error_handler.py,sha256=dqClsyDIcCPS6CtiqtGvEwTnORj24m2muXThXfHdhX0,2873
39
- dime_payments/http/transport.py,sha256=xYJ1OWHDTuTGuZgZPCLsV75AXEHUDyH1enBoIwwurac,2642
40
+ dime_payments/http/transport.py,sha256=tcFR_BLZJqti6mrWYRKKvYIEUJKMYG1mmv5FChA-b9Q,2642
40
41
  dime_payments/pagination/__init__.py,sha256=J2k8htn29Nu92KQh4wtEeN0StyMc8cWxVY8jQydF-dQ,62
41
42
  dime_payments/pagination/cursor_page.py,sha256=Xpbt33ceBy_HQlqe3wFbxA93bUfx3rHcqXfhVAkOQJ0,2263
42
43
  dime_payments/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -44,15 +45,15 @@ dime_payments/resources/abstract_resource.py,sha256=-XmnqJvk2tVNe44JeY_TTAzD4UiU
44
45
  dime_payments/resources/addresses.py,sha256=sg0q3YCnkdGlGM0onbBh_UsatrIu4VyzAA1BdZ1QRZ0,1713
45
46
  dime_payments/resources/customers.py,sha256=6_9gG9tc2uHN-onhzNFP0NO67NxGOYqbwZmF96vU4bo,1545
46
47
  dime_payments/resources/deposits.py,sha256=k8wE7kT_baHjXmXxzN9v6_2BqDb0GSUlINPRVtjmyUI,1213
47
- dime_payments/resources/invoices.py,sha256=_n2MPmBgn4IvQ9nhq9q8gSuVrxDDjfs5251S9PpywwA,7062
48
+ dime_payments/resources/invoices.py,sha256=e85DWM2PotddRH-TR-tiWKBtLDxJ8BaSCgsLQSN0HAM,7841
48
49
  dime_payments/resources/merchants.py,sha256=blVx7JIAps9ktdpLjjQofEnVkK_KgXOQP8beBPkHrkg,1370
49
50
  dime_payments/resources/payment_methods.py,sha256=KJWryjnZadyohG1r6pmULjwPIjPIjScSK7ekQYamvYA,1775
50
- dime_payments/resources/recurring_invoices.py,sha256=mn-I-0o_uac6w-8HiwIWS2y0ptCZpVLZm3F60cyhl8w,1431
51
+ dime_payments/resources/recurring_invoices.py,sha256=gJ4sNPvxv4qyNppsGZnW_orOfEVw4ROTa6yrN71Ax4g,1726
51
52
  dime_payments/resources/recurring_payments.py,sha256=DPihjc-C7oLLfPrP5UveNpEKHmUxayv4k3hFpguJ35c,2940
52
53
  dime_payments/resources/transactions.py,sha256=Q0VwYDicdGuM1WQXqxPX_KS313A0KZVCeuVCyKS_G7k,2742
53
54
  dime_payments/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
55
  dime_payments/support/arr.py,sha256=_mboi_q3HGn_Sx_MgU2exkbl-fnP2gdDY8yeSC8hkNc,1722
55
- dime_python_sdk-1.2.0.dist-info/METADATA,sha256=RwVXCQEwsDl3sm2uRGyIORwqFTAIP-o_6lQMuDVsGhk,12723
56
- dime_python_sdk-1.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
57
- dime_python_sdk-1.2.0.dist-info/licenses/LICENSE,sha256=-Xc5dLgfuFLVQLsSKNYmJKtNZCUU1_9ErffdGiwH-DU,1072
58
- dime_python_sdk-1.2.0.dist-info/RECORD,,
56
+ dime_python_sdk-1.3.0.dist-info/METADATA,sha256=xPcFnbu29Dk0nUZZythxZw8wRMHBF3j9ZzIhLQX6YSc,14703
57
+ dime_python_sdk-1.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
58
+ dime_python_sdk-1.3.0.dist-info/licenses/LICENSE,sha256=-Xc5dLgfuFLVQLsSKNYmJKtNZCUU1_9ErffdGiwH-DU,1072
59
+ dime_python_sdk-1.3.0.dist-info/RECORD,,