opensettle 0.1.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.
- opensettle-0.1.0/.gitignore +19 -0
- opensettle-0.1.0/CHANGELOG.md +18 -0
- opensettle-0.1.0/LICENSE +21 -0
- opensettle-0.1.0/PKG-INFO +206 -0
- opensettle-0.1.0/README.md +144 -0
- opensettle-0.1.0/pyproject.toml +188 -0
- opensettle-0.1.0/src/opensettle/__init__.py +54 -0
- opensettle-0.1.0/src/opensettle/_errors.py +255 -0
- opensettle-0.1.0/src/opensettle/_http.py +204 -0
- opensettle-0.1.0/src/opensettle/_http_async.py +194 -0
- opensettle-0.1.0/src/opensettle/_transport.py +242 -0
- opensettle-0.1.0/src/opensettle/_version.py +7 -0
- opensettle-0.1.0/src/opensettle/_webhooks.py +195 -0
- opensettle-0.1.0/src/opensettle/client.py +230 -0
- opensettle-0.1.0/src/opensettle/py.typed +0 -0
- opensettle-0.1.0/src/opensettle/resources/__init__.py +3 -0
- opensettle-0.1.0/src/opensettle/resources/checkouts.py +48 -0
- opensettle-0.1.0/src/opensettle/resources/customers.py +88 -0
- opensettle-0.1.0/src/opensettle/resources/invoices.py +118 -0
- opensettle-0.1.0/src/opensettle/resources/payments.py +100 -0
- opensettle-0.1.0/src/opensettle/resources/products.py +120 -0
- opensettle-0.1.0/src/opensettle/resources/subscriptions.py +179 -0
- opensettle-0.1.0/src/opensettle/resources/webhook_endpoints.py +183 -0
- opensettle-0.1.0/src/opensettle/types.py +331 -0
- opensettle-0.1.0/tests/conftest.py +78 -0
- opensettle-0.1.0/tests/helpers.py +38 -0
- opensettle-0.1.0/tests/test_client.py +201 -0
- opensettle-0.1.0/tests/test_errors_taxonomy.py +222 -0
- opensettle-0.1.0/tests/test_http_async.py +280 -0
- opensettle-0.1.0/tests/test_http_config.py +141 -0
- opensettle-0.1.0/tests/test_http_errors.py +209 -0
- opensettle-0.1.0/tests/test_http_request_shape.py +298 -0
- opensettle-0.1.0/tests/test_http_retries.py +274 -0
- opensettle-0.1.0/tests/test_resources_async_parity.py +437 -0
- opensettle-0.1.0/tests/test_resources_checkouts.py +154 -0
- opensettle-0.1.0/tests/test_resources_customers.py +159 -0
- opensettle-0.1.0/tests/test_resources_invoices.py +266 -0
- opensettle-0.1.0/tests/test_resources_payments.py +178 -0
- opensettle-0.1.0/tests/test_resources_products.py +240 -0
- opensettle-0.1.0/tests/test_resources_subscriptions.py +255 -0
- opensettle-0.1.0/tests/test_resources_webhook_endpoints.py +286 -0
- opensettle-0.1.0/tests/test_version.py +31 -0
- opensettle-0.1.0/tests/test_webhooks.py +348 -0
- opensettle-0.1.0/tests/test_webhooks_parity.py +133 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.pyright_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
.coverage.*
|
|
14
|
+
htmlcov/
|
|
15
|
+
coverage.xml
|
|
16
|
+
.tox/
|
|
17
|
+
.venv/
|
|
18
|
+
.python-version
|
|
19
|
+
uv.lock
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-05-12
|
|
4
|
+
|
|
5
|
+
Initial release. Mirrors the surface area of the Node SDK
|
|
6
|
+
(`@opensettle/sdk@0.2.0`).
|
|
7
|
+
|
|
8
|
+
- Sync `OpenSettle` and async `AsyncOpenSettle` clients sharing a
|
|
9
|
+
single transport core.
|
|
10
|
+
- 7 resources: customers, products, invoices, payments, subscriptions,
|
|
11
|
+
checkouts, webhook_endpoints.
|
|
12
|
+
- Typed error hierarchy mirroring the API's 12 stable error codes.
|
|
13
|
+
- HMAC-SHA256 signed-webhook verifier (`t=…,v1=…` scheme, constant-time
|
|
14
|
+
comparison, 300 s default tolerance).
|
|
15
|
+
- Idempotent writes by default on money-adjacent routes.
|
|
16
|
+
- Bounded retries with exponential backoff on 5xx, 429, and network
|
|
17
|
+
errors; `Retry-After` honored.
|
|
18
|
+
- `py.typed` marker; passes `mypy --strict` and `pyright` strict.
|
opensettle-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenSettle
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opensettle
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the OpenSettle API. Stablecoin billing on Base, Ethereum, Polygon, Arbitrum, Solana, and Tron.
|
|
5
|
+
Project-URL: Homepage, https://opensettle.io
|
|
6
|
+
Project-URL: Documentation, https://opensettle.io/docs/sdks
|
|
7
|
+
Project-URL: Repository, https://github.com/OpenSettle/opensettle-sdk-python
|
|
8
|
+
Project-URL: Issues, https://github.com/OpenSettle/opensettle-sdk-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/OpenSettle/opensettle-sdk-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: OpenSettle <support@opensettle.io>
|
|
11
|
+
License: MIT License
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 OpenSettle
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Keywords: billing,evm,non-custodial,opensettle,solana,stablecoin,subscriptions,tron,usdc,usdt
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
45
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
46
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
47
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
48
|
+
Classifier: Typing :: Typed
|
|
49
|
+
Requires-Python: >=3.9
|
|
50
|
+
Requires-Dist: httpx<1,>=0.27
|
|
51
|
+
Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: freezegun>=1.5; extra == 'dev'
|
|
54
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
55
|
+
Requires-Dist: pyright>=1.1.370; extra == 'dev'
|
|
56
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
57
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
58
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
59
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
60
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
61
|
+
Description-Content-Type: text/markdown
|
|
62
|
+
|
|
63
|
+
# opensettle
|
|
64
|
+
|
|
65
|
+
Official Python SDK for the [OpenSettle](https://opensettle.io) API.
|
|
66
|
+
|
|
67
|
+
Non-custodial stablecoin billing on Base, Ethereum, Polygon, Arbitrum,
|
|
68
|
+
Solana and Tron. Typed end-to-end, sync **and** async, signed-webhook
|
|
69
|
+
verifier included, idempotent writes by default.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install opensettle
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Requires Python 3.9+.
|
|
78
|
+
|
|
79
|
+
## Quickstart
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from opensettle import OpenSettle
|
|
83
|
+
|
|
84
|
+
os = OpenSettle(
|
|
85
|
+
api_key="sk_test_…", # or os.environ["OPENSETTLE_KEY"]
|
|
86
|
+
workspace_id="ws_…", # or os.environ["OPENSETTLE_WORKSPACE"]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
customer = os.customers.create(email="ada@example.com")
|
|
90
|
+
invoice = os.invoices.create(
|
|
91
|
+
customer_id=customer["id"],
|
|
92
|
+
amount_minor=19_900,
|
|
93
|
+
currency="USD",
|
|
94
|
+
chain="base",
|
|
95
|
+
token="USDC",
|
|
96
|
+
line_items=[
|
|
97
|
+
{"description": "Pro plan", "quantity": 1, "unit_amount_minor": 19_900}
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
os.invoices.send(invoice["id"])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Async
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import asyncio
|
|
107
|
+
from opensettle import AsyncOpenSettle
|
|
108
|
+
|
|
109
|
+
async def main() -> None:
|
|
110
|
+
async with AsyncOpenSettle(api_key="sk_test_…", workspace_id="ws_…") as os:
|
|
111
|
+
customer = await os.customers.create(email="ada@example.com")
|
|
112
|
+
print(customer["id"])
|
|
113
|
+
|
|
114
|
+
asyncio.run(main())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Webhooks
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from opensettle import verify_webhook, WebhookVerificationError
|
|
121
|
+
|
|
122
|
+
@app.post("/webhooks/opensettle")
|
|
123
|
+
async def handler(request):
|
|
124
|
+
raw = await request.body()
|
|
125
|
+
try:
|
|
126
|
+
event = verify_webhook(
|
|
127
|
+
raw_body=raw,
|
|
128
|
+
signature_header=request.headers.get("x-opensettle-signature"),
|
|
129
|
+
secret=os.environ["WHSEC"],
|
|
130
|
+
)
|
|
131
|
+
except WebhookVerificationError as e:
|
|
132
|
+
return Response(status=400, content=f"bad signature: {e.reason}")
|
|
133
|
+
# event.data is the decoded JSON; event.timestamp is the signed epoch
|
|
134
|
+
return Response(status=200)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Error handling
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from opensettle import (
|
|
141
|
+
OpenSettle,
|
|
142
|
+
RateLimitError,
|
|
143
|
+
SettlementError,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
os.checkouts.create(...)
|
|
148
|
+
except RateLimitError as e:
|
|
149
|
+
time.sleep(e.retry_after or 1)
|
|
150
|
+
retry()
|
|
151
|
+
except SettlementError as e:
|
|
152
|
+
if e.code == "insufficient_confirmations":
|
|
153
|
+
wait_and_retry()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The full error hierarchy:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
OpenSettleError
|
|
160
|
+
├── InvalidRequestError
|
|
161
|
+
├── InvalidStateTransitionError
|
|
162
|
+
├── AuthenticationError
|
|
163
|
+
├── ForbiddenError
|
|
164
|
+
├── NotFoundError
|
|
165
|
+
├── ConflictError
|
|
166
|
+
├── RateLimitError # carries `retry_after: float | None`
|
|
167
|
+
├── SettlementError # chain_reverted | insufficient_confirmations | signing_required
|
|
168
|
+
├── StepUpRequiredError # aal_required
|
|
169
|
+
├── APIError # internal_error or forward-compat unknown codes
|
|
170
|
+
└── NetworkError # transport-layer / timeout
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Every error carries `code`, `status`, `request_id`, and `param` so you
|
|
174
|
+
can quote the request ID in support tickets.
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
os = OpenSettle(
|
|
180
|
+
api_key="sk_test_…",
|
|
181
|
+
workspace_id="ws_…",
|
|
182
|
+
base_url="https://api.opensettle.io", # default; override for self-host
|
|
183
|
+
test_mode=None, # None | True | False env-assertion gate
|
|
184
|
+
timeout=30.0, # seconds; default 30s
|
|
185
|
+
max_network_retries=3, # 0 to disable retries
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`test_mode=True` refuses `sk_live_…` keys; `test_mode=False` refuses
|
|
190
|
+
`sk_test_…`. Leave `None` to accept either and let the API decide.
|
|
191
|
+
|
|
192
|
+
## Resources
|
|
193
|
+
|
|
194
|
+
| Resource | Methods |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `customers` | `list`, `retrieve`, `create`, `update`, `delete` |
|
|
197
|
+
| `products` | `list`, `retrieve`, `create`, `update`, `list_prices`, `create_price`, `delete`, `delete_price` |
|
|
198
|
+
| `invoices` | `list`, `retrieve`, `create`, `send`, `remind`, `void` |
|
|
199
|
+
| `payments` | `list`, `retrieve`, `refund`, `refund_broadcast` |
|
|
200
|
+
| `subscriptions` | `list`, `retrieve`, `create`, `pause`, `resume`, `cancel`, `change_plan` |
|
|
201
|
+
| `checkouts` | `create`, `retrieve` |
|
|
202
|
+
| `webhook_endpoints` | `list`, `retrieve`, `create`, `update`, `delete`, `rotate_secret`, `test` |
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# opensettle
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [OpenSettle](https://opensettle.io) API.
|
|
4
|
+
|
|
5
|
+
Non-custodial stablecoin billing on Base, Ethereum, Polygon, Arbitrum,
|
|
6
|
+
Solana and Tron. Typed end-to-end, sync **and** async, signed-webhook
|
|
7
|
+
verifier included, idempotent writes by default.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install opensettle
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.9+.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from opensettle import OpenSettle
|
|
21
|
+
|
|
22
|
+
os = OpenSettle(
|
|
23
|
+
api_key="sk_test_…", # or os.environ["OPENSETTLE_KEY"]
|
|
24
|
+
workspace_id="ws_…", # or os.environ["OPENSETTLE_WORKSPACE"]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
customer = os.customers.create(email="ada@example.com")
|
|
28
|
+
invoice = os.invoices.create(
|
|
29
|
+
customer_id=customer["id"],
|
|
30
|
+
amount_minor=19_900,
|
|
31
|
+
currency="USD",
|
|
32
|
+
chain="base",
|
|
33
|
+
token="USDC",
|
|
34
|
+
line_items=[
|
|
35
|
+
{"description": "Pro plan", "quantity": 1, "unit_amount_minor": 19_900}
|
|
36
|
+
],
|
|
37
|
+
)
|
|
38
|
+
os.invoices.send(invoice["id"])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Async
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
from opensettle import AsyncOpenSettle
|
|
46
|
+
|
|
47
|
+
async def main() -> None:
|
|
48
|
+
async with AsyncOpenSettle(api_key="sk_test_…", workspace_id="ws_…") as os:
|
|
49
|
+
customer = await os.customers.create(email="ada@example.com")
|
|
50
|
+
print(customer["id"])
|
|
51
|
+
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Webhooks
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from opensettle import verify_webhook, WebhookVerificationError
|
|
59
|
+
|
|
60
|
+
@app.post("/webhooks/opensettle")
|
|
61
|
+
async def handler(request):
|
|
62
|
+
raw = await request.body()
|
|
63
|
+
try:
|
|
64
|
+
event = verify_webhook(
|
|
65
|
+
raw_body=raw,
|
|
66
|
+
signature_header=request.headers.get("x-opensettle-signature"),
|
|
67
|
+
secret=os.environ["WHSEC"],
|
|
68
|
+
)
|
|
69
|
+
except WebhookVerificationError as e:
|
|
70
|
+
return Response(status=400, content=f"bad signature: {e.reason}")
|
|
71
|
+
# event.data is the decoded JSON; event.timestamp is the signed epoch
|
|
72
|
+
return Response(status=200)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Error handling
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from opensettle import (
|
|
79
|
+
OpenSettle,
|
|
80
|
+
RateLimitError,
|
|
81
|
+
SettlementError,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
os.checkouts.create(...)
|
|
86
|
+
except RateLimitError as e:
|
|
87
|
+
time.sleep(e.retry_after or 1)
|
|
88
|
+
retry()
|
|
89
|
+
except SettlementError as e:
|
|
90
|
+
if e.code == "insufficient_confirmations":
|
|
91
|
+
wait_and_retry()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The full error hierarchy:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
OpenSettleError
|
|
98
|
+
├── InvalidRequestError
|
|
99
|
+
├── InvalidStateTransitionError
|
|
100
|
+
├── AuthenticationError
|
|
101
|
+
├── ForbiddenError
|
|
102
|
+
├── NotFoundError
|
|
103
|
+
├── ConflictError
|
|
104
|
+
├── RateLimitError # carries `retry_after: float | None`
|
|
105
|
+
├── SettlementError # chain_reverted | insufficient_confirmations | signing_required
|
|
106
|
+
├── StepUpRequiredError # aal_required
|
|
107
|
+
├── APIError # internal_error or forward-compat unknown codes
|
|
108
|
+
└── NetworkError # transport-layer / timeout
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Every error carries `code`, `status`, `request_id`, and `param` so you
|
|
112
|
+
can quote the request ID in support tickets.
|
|
113
|
+
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
os = OpenSettle(
|
|
118
|
+
api_key="sk_test_…",
|
|
119
|
+
workspace_id="ws_…",
|
|
120
|
+
base_url="https://api.opensettle.io", # default; override for self-host
|
|
121
|
+
test_mode=None, # None | True | False env-assertion gate
|
|
122
|
+
timeout=30.0, # seconds; default 30s
|
|
123
|
+
max_network_retries=3, # 0 to disable retries
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`test_mode=True` refuses `sk_live_…` keys; `test_mode=False` refuses
|
|
128
|
+
`sk_test_…`. Leave `None` to accept either and let the API decide.
|
|
129
|
+
|
|
130
|
+
## Resources
|
|
131
|
+
|
|
132
|
+
| Resource | Methods |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `customers` | `list`, `retrieve`, `create`, `update`, `delete` |
|
|
135
|
+
| `products` | `list`, `retrieve`, `create`, `update`, `list_prices`, `create_price`, `delete`, `delete_price` |
|
|
136
|
+
| `invoices` | `list`, `retrieve`, `create`, `send`, `remind`, `void` |
|
|
137
|
+
| `payments` | `list`, `retrieve`, `refund`, `refund_broadcast` |
|
|
138
|
+
| `subscriptions` | `list`, `retrieve`, `create`, `pause`, `resume`, `cancel`, `change_plan` |
|
|
139
|
+
| `checkouts` | `create`, `retrieve` |
|
|
140
|
+
| `webhook_endpoints` | `list`, `retrieve`, `create`, `update`, `delete`, `rotate_secret`, `test` |
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "opensettle"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the OpenSettle API. Stablecoin billing on Base, Ethereum, Polygon, Arbitrum, Solana, and Tron."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "OpenSettle", email = "support@opensettle.io" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"opensettle",
|
|
15
|
+
"stablecoin",
|
|
16
|
+
"billing",
|
|
17
|
+
"subscriptions",
|
|
18
|
+
"usdc",
|
|
19
|
+
"usdt",
|
|
20
|
+
"evm",
|
|
21
|
+
"solana",
|
|
22
|
+
"tron",
|
|
23
|
+
"non-custodial",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.9",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
38
|
+
"Topic :: Office/Business :: Financial",
|
|
39
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
40
|
+
"Typing :: Typed",
|
|
41
|
+
]
|
|
42
|
+
dependencies = [
|
|
43
|
+
"httpx>=0.27,<1",
|
|
44
|
+
"typing-extensions>=4.7; python_version < '3.11'",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://opensettle.io"
|
|
49
|
+
Documentation = "https://opensettle.io/docs/sdks"
|
|
50
|
+
Repository = "https://github.com/OpenSettle/opensettle-sdk-python"
|
|
51
|
+
Issues = "https://github.com/OpenSettle/opensettle-sdk-python/issues"
|
|
52
|
+
Changelog = "https://github.com/OpenSettle/opensettle-sdk-python/blob/main/CHANGELOG.md"
|
|
53
|
+
|
|
54
|
+
[project.optional-dependencies]
|
|
55
|
+
dev = [
|
|
56
|
+
"pytest>=8.0",
|
|
57
|
+
"pytest-asyncio>=0.23",
|
|
58
|
+
"pytest-cov>=5.0",
|
|
59
|
+
"respx>=0.21",
|
|
60
|
+
"freezegun>=1.5",
|
|
61
|
+
"mypy>=1.10",
|
|
62
|
+
"pyright>=1.1.370",
|
|
63
|
+
"ruff>=0.6",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.hatch.build.targets.wheel]
|
|
67
|
+
packages = ["src/opensettle"]
|
|
68
|
+
|
|
69
|
+
[tool.hatch.build.targets.sdist]
|
|
70
|
+
include = [
|
|
71
|
+
"/src",
|
|
72
|
+
"/tests",
|
|
73
|
+
"/README.md",
|
|
74
|
+
"/CHANGELOG.md",
|
|
75
|
+
"/LICENSE",
|
|
76
|
+
"/pyproject.toml",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[tool.pytest.ini_options]
|
|
80
|
+
minversion = "8.0"
|
|
81
|
+
testpaths = ["tests"]
|
|
82
|
+
addopts = [
|
|
83
|
+
"-ra",
|
|
84
|
+
"--strict-markers",
|
|
85
|
+
"--strict-config",
|
|
86
|
+
]
|
|
87
|
+
asyncio_mode = "auto"
|
|
88
|
+
filterwarnings = [
|
|
89
|
+
"error",
|
|
90
|
+
"ignore::DeprecationWarning:respx.*",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
[tool.coverage.run]
|
|
94
|
+
branch = true
|
|
95
|
+
source = ["opensettle"]
|
|
96
|
+
|
|
97
|
+
[tool.coverage.report]
|
|
98
|
+
show_missing = true
|
|
99
|
+
exclude_lines = [
|
|
100
|
+
"pragma: no cover",
|
|
101
|
+
"raise NotImplementedError",
|
|
102
|
+
"if TYPE_CHECKING:",
|
|
103
|
+
"if typing.TYPE_CHECKING:",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff]
|
|
107
|
+
target-version = "py39"
|
|
108
|
+
line-length = 100
|
|
109
|
+
src = ["src", "tests"]
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint]
|
|
112
|
+
select = [
|
|
113
|
+
"E", # pycodestyle errors
|
|
114
|
+
"W", # pycodestyle warnings
|
|
115
|
+
"F", # pyflakes
|
|
116
|
+
"I", # isort
|
|
117
|
+
"B", # bugbear
|
|
118
|
+
"UP", # pyupgrade
|
|
119
|
+
"SIM", # simplify
|
|
120
|
+
"PT", # pytest style
|
|
121
|
+
"RUF", # ruff-specific
|
|
122
|
+
"TID", # tidy imports
|
|
123
|
+
"PIE", # misc lints
|
|
124
|
+
"PYI", # stub files
|
|
125
|
+
"S", # security
|
|
126
|
+
]
|
|
127
|
+
ignore = [
|
|
128
|
+
"E501", # line length handled by formatter
|
|
129
|
+
"S101", # asserts are fine in tests
|
|
130
|
+
"S311", # standard-random is fine for idempotency-key fallback
|
|
131
|
+
"PT011", # broad raises in parametrize — explicit message coverage instead
|
|
132
|
+
# 3.9-compat: keep ``Optional[X]`` / ``Union[X, Y]`` / ``Dict[…]`` forms.
|
|
133
|
+
# We have ``from __future__ import annotations`` everywhere so the
|
|
134
|
+
# modern syntax would work for *annotations*, but the SDK also uses
|
|
135
|
+
# these in non-annotation positions (``cast``, ``isinstance``-adjacent
|
|
136
|
+
# type aliases) and the matching industry practice from openai-python
|
|
137
|
+
# and anthropic-sdk is to ship the ``Optional[X]`` form for max reach.
|
|
138
|
+
"UP006",
|
|
139
|
+
"UP007",
|
|
140
|
+
"UP045",
|
|
141
|
+
# Relative imports between sibling private modules within the package
|
|
142
|
+
# are idiomatic and intended; ``from ._http import HttpClient`` is the
|
|
143
|
+
# right form, not ``from opensettle._http import HttpClient``.
|
|
144
|
+
"TID252",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
[tool.ruff.lint.per-file-ignores]
|
|
148
|
+
"tests/**" = ["S", "B", "PT006", "PT007", "PT017", "SIM117"]
|
|
149
|
+
"src/opensettle/types.py" = ["UP035"]
|
|
150
|
+
"src/opensettle/resources/subscriptions.py" = ["UP035"]
|
|
151
|
+
"src/opensettle/resources/webhook_endpoints.py" = ["UP035"]
|
|
152
|
+
"src/opensettle/_errors.py" = ["UP035"]
|
|
153
|
+
"src/opensettle/_webhooks.py" = ["UP035"]
|
|
154
|
+
|
|
155
|
+
[tool.ruff.format]
|
|
156
|
+
quote-style = "double"
|
|
157
|
+
indent-style = "space"
|
|
158
|
+
|
|
159
|
+
[tool.mypy]
|
|
160
|
+
# Source supports Python 3.9 at runtime (see ``requires-python`` and the
|
|
161
|
+
# test suite running on 3.11). Static analysis runs against 3.10+ because
|
|
162
|
+
# modern mypy dropped 3.9 type-check support; we still verify 3.9 runtime
|
|
163
|
+
# behavior through the pytest matrix.
|
|
164
|
+
python_version = "3.10"
|
|
165
|
+
strict = true
|
|
166
|
+
warn_unreachable = true
|
|
167
|
+
warn_unused_ignores = true
|
|
168
|
+
warn_redundant_casts = true
|
|
169
|
+
no_implicit_reexport = false
|
|
170
|
+
show_error_codes = true
|
|
171
|
+
files = ["src/opensettle"]
|
|
172
|
+
|
|
173
|
+
[[tool.mypy.overrides]]
|
|
174
|
+
module = "tests.*"
|
|
175
|
+
disallow_untyped_defs = false
|
|
176
|
+
check_untyped_defs = true
|
|
177
|
+
|
|
178
|
+
[tool.pyright]
|
|
179
|
+
include = ["src/opensettle"]
|
|
180
|
+
pythonVersion = "3.10"
|
|
181
|
+
# Standard mode rather than strict: strict reports false positives on
|
|
182
|
+
# our dynamic-JSON envelope-parsing path (every ``Any.get()`` becomes an
|
|
183
|
+
# "Unknown" propagation error). The mypy --strict run is the primary
|
|
184
|
+
# typecheck gate; pyright standard is the cross-checker.
|
|
185
|
+
typeCheckingMode = "standard"
|
|
186
|
+
reportMissingTypeStubs = false
|
|
187
|
+
venvPath = "."
|
|
188
|
+
venv = ".venv"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Official Python SDK for the OpenSettle API.
|
|
2
|
+
|
|
3
|
+
Public surface is intentionally narrow: the high-level clients
|
|
4
|
+
(:class:`OpenSettle`, :class:`AsyncOpenSettle`), the typed error
|
|
5
|
+
hierarchy, and the webhook verifier. Everything else is private and may
|
|
6
|
+
change without a major-version bump.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from ._errors import (
|
|
12
|
+
APIError,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
ConflictError,
|
|
15
|
+
ErrorCode,
|
|
16
|
+
ForbiddenError,
|
|
17
|
+
InvalidRequestError,
|
|
18
|
+
InvalidStateTransitionError,
|
|
19
|
+
NetworkError,
|
|
20
|
+
NotFoundError,
|
|
21
|
+
OpenSettleError,
|
|
22
|
+
RateLimitError,
|
|
23
|
+
SettlementError,
|
|
24
|
+
StepUpRequiredError,
|
|
25
|
+
)
|
|
26
|
+
from ._version import SDK_VERSION
|
|
27
|
+
from ._webhooks import (
|
|
28
|
+
VerifiedWebhook,
|
|
29
|
+
WebhookVerificationError,
|
|
30
|
+
verify_webhook,
|
|
31
|
+
)
|
|
32
|
+
from .client import AsyncOpenSettle, OpenSettle
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"SDK_VERSION",
|
|
36
|
+
"APIError",
|
|
37
|
+
"AsyncOpenSettle",
|
|
38
|
+
"AuthenticationError",
|
|
39
|
+
"ConflictError",
|
|
40
|
+
"ErrorCode",
|
|
41
|
+
"ForbiddenError",
|
|
42
|
+
"InvalidRequestError",
|
|
43
|
+
"InvalidStateTransitionError",
|
|
44
|
+
"NetworkError",
|
|
45
|
+
"NotFoundError",
|
|
46
|
+
"OpenSettle",
|
|
47
|
+
"OpenSettleError",
|
|
48
|
+
"RateLimitError",
|
|
49
|
+
"SettlementError",
|
|
50
|
+
"StepUpRequiredError",
|
|
51
|
+
"VerifiedWebhook",
|
|
52
|
+
"WebhookVerificationError",
|
|
53
|
+
"verify_webhook",
|
|
54
|
+
]
|