speedapi-lib 0.1.4__tar.gz → 2.0.2__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.
- speedapi_lib-2.0.2/LICENSE +21 -0
- speedapi_lib-2.0.2/PKG-INFO +306 -0
- speedapi_lib-2.0.2/README.md +268 -0
- speedapi_lib-2.0.2/pyproject.toml +89 -0
- {speedapi_lib-0.1.4 → speedapi_lib-2.0.2}/setup.cfg +4 -4
- speedapi_lib-2.0.2/src/speedapi/__init__.py +41 -0
- speedapi_lib-2.0.2/src/speedapi/_auth.py +26 -0
- speedapi_lib-2.0.2/src/speedapi/_http.py +233 -0
- speedapi_lib-2.0.2/src/speedapi/async_client.py +97 -0
- speedapi_lib-2.0.2/src/speedapi/client.py +112 -0
- speedapi_lib-2.0.2/src/speedapi/exceptions.py +71 -0
- speedapi_lib-2.0.2/src/speedapi/models.py +307 -0
- speedapi_lib-2.0.2/src/speedapi/resources/__init__.py +1 -0
- speedapi_lib-2.0.2/src/speedapi/resources/checkout_sessions.py +217 -0
- speedapi_lib-2.0.2/src/speedapi/resources/invoices.py +181 -0
- speedapi_lib-2.0.2/src/speedapi/resources/pay_requests.py +175 -0
- speedapi_lib-2.0.2/src/speedapi/resources/webhooks.py +127 -0
- speedapi_lib-2.0.2/src/speedapi_lib.egg-info/PKG-INFO +306 -0
- speedapi_lib-2.0.2/src/speedapi_lib.egg-info/SOURCES.txt +28 -0
- speedapi_lib-2.0.2/src/speedapi_lib.egg-info/requires.txt +12 -0
- speedapi_lib-2.0.2/src/speedapi_lib.egg-info/top_level.txt +1 -0
- speedapi_lib-2.0.2/tests/test_async_client.py +104 -0
- speedapi_lib-2.0.2/tests/test_balances.py +50 -0
- speedapi_lib-2.0.2/tests/test_checkout_sessions.py +93 -0
- speedapi_lib-2.0.2/tests/test_exceptions.py +90 -0
- speedapi_lib-2.0.2/tests/test_invoices.py +65 -0
- speedapi_lib-2.0.2/tests/test_models.py +103 -0
- speedapi_lib-2.0.2/tests/test_pay_requests.py +60 -0
- speedapi_lib-2.0.2/tests/test_webhooks.py +78 -0
- speedapi_lib-0.1.4/PKG-INFO +0 -65
- speedapi_lib-0.1.4/README.md +0 -41
- speedapi_lib-0.1.4/setup.py +0 -29
- speedapi_lib-0.1.4/speedapi_lib/__init__.py +0 -0
- speedapi_lib-0.1.4/speedapi_lib/speedapi.py +0 -56
- speedapi_lib-0.1.4/speedapi_lib.egg-info/PKG-INFO +0 -65
- speedapi_lib-0.1.4/speedapi_lib.egg-info/SOURCES.txt +0 -12
- speedapi_lib-0.1.4/speedapi_lib.egg-info/entry_points.txt +0 -2
- speedapi_lib-0.1.4/speedapi_lib.egg-info/requires.txt +0 -2
- speedapi_lib-0.1.4/speedapi_lib.egg-info/top_level.txt +0 -2
- speedapi_lib-0.1.4/tests/__init__.py +0 -0
- speedapi_lib-0.1.4/tests/test_sample.py +0 -14
- {speedapi_lib-0.1.4 → speedapi_lib-2.0.2/src}/speedapi_lib.egg-info/dependency_links.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Furkan Köykıran
|
|
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,306 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: speedapi-lib
|
|
3
|
+
Version: 2.0.2
|
|
4
|
+
Summary: Python SDK for the Speed Merchant API — sync & async, Pydantic v2, full type hints
|
|
5
|
+
Author-email: Furkan Köykıran <furkankoykiran@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/furkankoykiran/speedapi_lib
|
|
8
|
+
Project-URL: Documentation, https://docs.tryspeed.com
|
|
9
|
+
Project-URL: Repository, https://github.com/furkankoykiran/speedapi_lib
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/furkankoykiran/speedapi_lib/issues
|
|
11
|
+
Keywords: speed,merchant,api,bitcoin,lightning,payments
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Requires-Dist: pydantic>=2.5
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
31
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
35
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
36
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
|
|
41
|
+
# ⚡ speedapi-python
|
|
42
|
+
|
|
43
|
+
**The official Python SDK for the [Speed Merchant API](https://docs.tryspeed.com)**
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/speedapi-python/)
|
|
46
|
+
[](https://pypi.org/project/speedapi-python/)
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
[](https://mypy-lang.org/)
|
|
49
|
+
|
|
50
|
+
> Sync & async support · Pydantic v2 models · Full type hints · Webhook verification
|
|
51
|
+
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- 🔄 **Sync & Async** clients powered by `httpx`
|
|
59
|
+
- 🧩 **Pydantic v2** request / response models with IDE autocomplete
|
|
60
|
+
- 🛡️ **Rich error hierarchy** — `AuthenticationError`, `RateLimitError`, `NotFoundError` and more
|
|
61
|
+
- 🔗 **Checkout Sessions**, **Invoices**, **Pay Requests**, **Balances**
|
|
62
|
+
- 🔒 **Webhook signature verification** (HMAC-SHA256 with replay protection)
|
|
63
|
+
- 🐍 Python 3.9+ with 100% type annotations
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install speedapi-python
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
### Synchronous
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from speedapi import SpeedAPI
|
|
81
|
+
|
|
82
|
+
client = SpeedAPI(api_key="sk_live_...")
|
|
83
|
+
|
|
84
|
+
# Check balance
|
|
85
|
+
balance = client.balances.retrieve()
|
|
86
|
+
sats = client.balances.retrieve_sats()
|
|
87
|
+
print(f"Available: {sats} SATS")
|
|
88
|
+
|
|
89
|
+
# Create a checkout session
|
|
90
|
+
session = client.checkout_sessions.create(
|
|
91
|
+
amount=5000, # amount in cents (for USD)
|
|
92
|
+
currency="USD",
|
|
93
|
+
success_url="https://example.com/success",
|
|
94
|
+
cancel_url="https://example.com/cancel",
|
|
95
|
+
description="Premium subscription",
|
|
96
|
+
)
|
|
97
|
+
print(f"Redirect to: {session.url}")
|
|
98
|
+
|
|
99
|
+
# Create a Lightning pay request
|
|
100
|
+
pr = client.pay_requests.create(
|
|
101
|
+
amount=21000,
|
|
102
|
+
currency="SATS",
|
|
103
|
+
description="Coffee ☕",
|
|
104
|
+
)
|
|
105
|
+
print(f"Pay via: {pr.lightning_invoice}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Asynchronous (async/await)
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import asyncio
|
|
112
|
+
from speedapi import AsyncSpeedAPI
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
async with AsyncSpeedAPI(api_key="sk_live_...") as client:
|
|
116
|
+
# All resources work identically — just add await
|
|
117
|
+
balance = await client.balances.retrieve()
|
|
118
|
+
|
|
119
|
+
session = await client.checkout_sessions.create(
|
|
120
|
+
amount=5000,
|
|
121
|
+
currency="USD",
|
|
122
|
+
success_url="https://example.com/success",
|
|
123
|
+
)
|
|
124
|
+
print(session.url)
|
|
125
|
+
|
|
126
|
+
asyncio.run(main())
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### FastAPI Integration Example
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from fastapi import FastAPI, Request
|
|
133
|
+
from speedapi import AsyncSpeedAPI
|
|
134
|
+
|
|
135
|
+
app = FastAPI()
|
|
136
|
+
speed = AsyncSpeedAPI(api_key="sk_live_...")
|
|
137
|
+
|
|
138
|
+
@app.post("/create-session")
|
|
139
|
+
async def create_session():
|
|
140
|
+
session = await speed.checkout_sessions.create(
|
|
141
|
+
amount=999,
|
|
142
|
+
currency="USD",
|
|
143
|
+
success_url="https://myapp.com/success",
|
|
144
|
+
)
|
|
145
|
+
return {"checkout_url": session.url}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### `SpeedAPI` / `AsyncSpeedAPI`
|
|
153
|
+
|
|
154
|
+
| Parameter | Type | Default | Description |
|
|
155
|
+
|-----------|------|---------|-------------|
|
|
156
|
+
| `api_key` | `str` | required | Your Speed secret key (`sk_live_...`) |
|
|
157
|
+
| `base_url` | `str` | `https://api.tryspeed.com` | API base URL |
|
|
158
|
+
| `timeout` | `float` | `30.0` | Request timeout in seconds |
|
|
159
|
+
|
|
160
|
+
### Resources
|
|
161
|
+
|
|
162
|
+
#### `client.balances`
|
|
163
|
+
|
|
164
|
+
| Method | Returns | Description |
|
|
165
|
+
|--------|---------|-------------|
|
|
166
|
+
| `retrieve()` | `Balance` | All available/pending balances |
|
|
167
|
+
| `retrieve_sats()` | `float` | Available SATS balance (convenience) |
|
|
168
|
+
|
|
169
|
+
#### `client.checkout_sessions`
|
|
170
|
+
|
|
171
|
+
| Method | Returns | Description |
|
|
172
|
+
|--------|---------|-------------|
|
|
173
|
+
| `create(amount, currency, ...)` | `CheckoutSession` | Create a hosted checkout page |
|
|
174
|
+
| `retrieve(session_id)` | `CheckoutSession` | Get a session by ID |
|
|
175
|
+
| `list(limit, ...)` | `CheckoutSessionList` | List sessions (paginated) |
|
|
176
|
+
| `expire(session_id)` | `CheckoutSession` | Expire an open session |
|
|
177
|
+
|
|
178
|
+
#### `client.invoices`
|
|
179
|
+
|
|
180
|
+
| Method | Returns | Description |
|
|
181
|
+
|--------|---------|-------------|
|
|
182
|
+
| `create(amount, currency, ...)` | `Invoice` | Create a new invoice |
|
|
183
|
+
| `retrieve(invoice_id)` | `Invoice` | Get an invoice by ID |
|
|
184
|
+
| `list(limit, ...)` | `InvoiceList` | List invoices (paginated) |
|
|
185
|
+
|
|
186
|
+
#### `client.pay_requests`
|
|
187
|
+
|
|
188
|
+
| Method | Returns | Description |
|
|
189
|
+
|--------|---------|-------------|
|
|
190
|
+
| `create(amount, currency, ...)` | `PayRequest` | Create a pay request (Lightning) |
|
|
191
|
+
| `retrieve(pay_request_id)` | `PayRequest` | Get a pay request by ID |
|
|
192
|
+
| `list(limit, ...)` | `PayRequestList` | List pay requests (paginated) |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Error Handling
|
|
197
|
+
|
|
198
|
+
`speedapi-python` raises specific exceptions for every error class — no need to inspect raw status codes.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from speedapi import (
|
|
202
|
+
SpeedAPI,
|
|
203
|
+
AuthenticationError,
|
|
204
|
+
RateLimitError,
|
|
205
|
+
NotFoundError,
|
|
206
|
+
APIStatusError,
|
|
207
|
+
APIConnectionError,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
client = SpeedAPI(api_key="sk_live_...")
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
session = client.checkout_sessions.retrieve("cs_nonexistent")
|
|
214
|
+
except AuthenticationError:
|
|
215
|
+
print("Invalid API key — check your credentials")
|
|
216
|
+
except NotFoundError:
|
|
217
|
+
print("Session not found")
|
|
218
|
+
except RateLimitError:
|
|
219
|
+
print("Too many requests — back off and retry")
|
|
220
|
+
except APIConnectionError as e:
|
|
221
|
+
print(f"Network error: {e}")
|
|
222
|
+
except APIStatusError as e:
|
|
223
|
+
print(f"Unexpected API error {e.status_code}: {e.response_body}")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Exception Hierarchy
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
SpeedAPIError
|
|
230
|
+
├── AuthenticationError # HTTP 401
|
|
231
|
+
├── PermissionDeniedError # HTTP 403
|
|
232
|
+
├── NotFoundError # HTTP 404
|
|
233
|
+
├── RateLimitError # HTTP 429
|
|
234
|
+
├── APIStatusError # other 4xx / 5xx (carries .status_code + .response_body)
|
|
235
|
+
└── APIConnectionError # network-level (timeout, DNS, etc.)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Webhook Verification
|
|
241
|
+
|
|
242
|
+
Speed signs every webhook with HMAC-SHA256. Always verify the signature before processing.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
246
|
+
from speedapi.resources.webhooks import Webhooks, WebhookSignatureVerificationError
|
|
247
|
+
|
|
248
|
+
app = FastAPI()
|
|
249
|
+
WEBHOOK_SECRET = "whsec_..." # from your Speed Dashboard
|
|
250
|
+
|
|
251
|
+
@app.post("/webhooks/speed")
|
|
252
|
+
async def speed_webhook(request: Request):
|
|
253
|
+
payload = await request.body()
|
|
254
|
+
sig_header = request.headers.get("Speed-Signature", "")
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
event = Webhooks.construct_event(
|
|
258
|
+
payload=payload,
|
|
259
|
+
sig_header=sig_header,
|
|
260
|
+
secret=WEBHOOK_SECRET,
|
|
261
|
+
)
|
|
262
|
+
except WebhookSignatureVerificationError as e:
|
|
263
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
264
|
+
|
|
265
|
+
if event.type == "checkout_session.completed":
|
|
266
|
+
session_id = event.data.get("object", {}).get("id")
|
|
267
|
+
print(f"✅ Checkout completed: {session_id}")
|
|
268
|
+
elif event.type == "invoice.paid":
|
|
269
|
+
invoice_id = event.data.get("object", {}).get("id")
|
|
270
|
+
print(f"💰 Invoice paid: {invoice_id}")
|
|
271
|
+
|
|
272
|
+
return {"received": True}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
> **Security**: The SDK automatically rejects webhooks older than 5 minutes to prevent replay attacks.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Development
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Clone the repo
|
|
283
|
+
git clone https://github.com/furkankoykiran/speedapi_lib
|
|
284
|
+
cd speedapi_lib
|
|
285
|
+
|
|
286
|
+
# Install in editable mode with dev dependencies
|
|
287
|
+
pip install -e ".[dev]"
|
|
288
|
+
|
|
289
|
+
# Run the test suite
|
|
290
|
+
pytest tests/ -v --tb=short --cov=src/speedapi
|
|
291
|
+
|
|
292
|
+
# Type check
|
|
293
|
+
mypy src/speedapi
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
<div align="center">
|
|
305
|
+
Built with ❤️ by <a href="https://github.com/furkankoykiran">Furkan Köykıran</a>
|
|
306
|
+
</div>
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ speedapi-python
|
|
4
|
+
|
|
5
|
+
**The official Python SDK for the [Speed Merchant API](https://docs.tryspeed.com)**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/speedapi-python/)
|
|
8
|
+
[](https://pypi.org/project/speedapi-python/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://mypy-lang.org/)
|
|
11
|
+
|
|
12
|
+
> Sync & async support · Pydantic v2 models · Full type hints · Webhook verification
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- 🔄 **Sync & Async** clients powered by `httpx`
|
|
21
|
+
- 🧩 **Pydantic v2** request / response models with IDE autocomplete
|
|
22
|
+
- 🛡️ **Rich error hierarchy** — `AuthenticationError`, `RateLimitError`, `NotFoundError` and more
|
|
23
|
+
- 🔗 **Checkout Sessions**, **Invoices**, **Pay Requests**, **Balances**
|
|
24
|
+
- 🔒 **Webhook signature verification** (HMAC-SHA256 with replay protection)
|
|
25
|
+
- 🐍 Python 3.9+ with 100% type annotations
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install speedapi-python
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### Synchronous
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from speedapi import SpeedAPI
|
|
43
|
+
|
|
44
|
+
client = SpeedAPI(api_key="sk_live_...")
|
|
45
|
+
|
|
46
|
+
# Check balance
|
|
47
|
+
balance = client.balances.retrieve()
|
|
48
|
+
sats = client.balances.retrieve_sats()
|
|
49
|
+
print(f"Available: {sats} SATS")
|
|
50
|
+
|
|
51
|
+
# Create a checkout session
|
|
52
|
+
session = client.checkout_sessions.create(
|
|
53
|
+
amount=5000, # amount in cents (for USD)
|
|
54
|
+
currency="USD",
|
|
55
|
+
success_url="https://example.com/success",
|
|
56
|
+
cancel_url="https://example.com/cancel",
|
|
57
|
+
description="Premium subscription",
|
|
58
|
+
)
|
|
59
|
+
print(f"Redirect to: {session.url}")
|
|
60
|
+
|
|
61
|
+
# Create a Lightning pay request
|
|
62
|
+
pr = client.pay_requests.create(
|
|
63
|
+
amount=21000,
|
|
64
|
+
currency="SATS",
|
|
65
|
+
description="Coffee ☕",
|
|
66
|
+
)
|
|
67
|
+
print(f"Pay via: {pr.lightning_invoice}")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Asynchronous (async/await)
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import asyncio
|
|
74
|
+
from speedapi import AsyncSpeedAPI
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
async with AsyncSpeedAPI(api_key="sk_live_...") as client:
|
|
78
|
+
# All resources work identically — just add await
|
|
79
|
+
balance = await client.balances.retrieve()
|
|
80
|
+
|
|
81
|
+
session = await client.checkout_sessions.create(
|
|
82
|
+
amount=5000,
|
|
83
|
+
currency="USD",
|
|
84
|
+
success_url="https://example.com/success",
|
|
85
|
+
)
|
|
86
|
+
print(session.url)
|
|
87
|
+
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### FastAPI Integration Example
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from fastapi import FastAPI, Request
|
|
95
|
+
from speedapi import AsyncSpeedAPI
|
|
96
|
+
|
|
97
|
+
app = FastAPI()
|
|
98
|
+
speed = AsyncSpeedAPI(api_key="sk_live_...")
|
|
99
|
+
|
|
100
|
+
@app.post("/create-session")
|
|
101
|
+
async def create_session():
|
|
102
|
+
session = await speed.checkout_sessions.create(
|
|
103
|
+
amount=999,
|
|
104
|
+
currency="USD",
|
|
105
|
+
success_url="https://myapp.com/success",
|
|
106
|
+
)
|
|
107
|
+
return {"checkout_url": session.url}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### `SpeedAPI` / `AsyncSpeedAPI`
|
|
115
|
+
|
|
116
|
+
| Parameter | Type | Default | Description |
|
|
117
|
+
|-----------|------|---------|-------------|
|
|
118
|
+
| `api_key` | `str` | required | Your Speed secret key (`sk_live_...`) |
|
|
119
|
+
| `base_url` | `str` | `https://api.tryspeed.com` | API base URL |
|
|
120
|
+
| `timeout` | `float` | `30.0` | Request timeout in seconds |
|
|
121
|
+
|
|
122
|
+
### Resources
|
|
123
|
+
|
|
124
|
+
#### `client.balances`
|
|
125
|
+
|
|
126
|
+
| Method | Returns | Description |
|
|
127
|
+
|--------|---------|-------------|
|
|
128
|
+
| `retrieve()` | `Balance` | All available/pending balances |
|
|
129
|
+
| `retrieve_sats()` | `float` | Available SATS balance (convenience) |
|
|
130
|
+
|
|
131
|
+
#### `client.checkout_sessions`
|
|
132
|
+
|
|
133
|
+
| Method | Returns | Description |
|
|
134
|
+
|--------|---------|-------------|
|
|
135
|
+
| `create(amount, currency, ...)` | `CheckoutSession` | Create a hosted checkout page |
|
|
136
|
+
| `retrieve(session_id)` | `CheckoutSession` | Get a session by ID |
|
|
137
|
+
| `list(limit, ...)` | `CheckoutSessionList` | List sessions (paginated) |
|
|
138
|
+
| `expire(session_id)` | `CheckoutSession` | Expire an open session |
|
|
139
|
+
|
|
140
|
+
#### `client.invoices`
|
|
141
|
+
|
|
142
|
+
| Method | Returns | Description |
|
|
143
|
+
|--------|---------|-------------|
|
|
144
|
+
| `create(amount, currency, ...)` | `Invoice` | Create a new invoice |
|
|
145
|
+
| `retrieve(invoice_id)` | `Invoice` | Get an invoice by ID |
|
|
146
|
+
| `list(limit, ...)` | `InvoiceList` | List invoices (paginated) |
|
|
147
|
+
|
|
148
|
+
#### `client.pay_requests`
|
|
149
|
+
|
|
150
|
+
| Method | Returns | Description |
|
|
151
|
+
|--------|---------|-------------|
|
|
152
|
+
| `create(amount, currency, ...)` | `PayRequest` | Create a pay request (Lightning) |
|
|
153
|
+
| `retrieve(pay_request_id)` | `PayRequest` | Get a pay request by ID |
|
|
154
|
+
| `list(limit, ...)` | `PayRequestList` | List pay requests (paginated) |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
`speedapi-python` raises specific exceptions for every error class — no need to inspect raw status codes.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from speedapi import (
|
|
164
|
+
SpeedAPI,
|
|
165
|
+
AuthenticationError,
|
|
166
|
+
RateLimitError,
|
|
167
|
+
NotFoundError,
|
|
168
|
+
APIStatusError,
|
|
169
|
+
APIConnectionError,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
client = SpeedAPI(api_key="sk_live_...")
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
session = client.checkout_sessions.retrieve("cs_nonexistent")
|
|
176
|
+
except AuthenticationError:
|
|
177
|
+
print("Invalid API key — check your credentials")
|
|
178
|
+
except NotFoundError:
|
|
179
|
+
print("Session not found")
|
|
180
|
+
except RateLimitError:
|
|
181
|
+
print("Too many requests — back off and retry")
|
|
182
|
+
except APIConnectionError as e:
|
|
183
|
+
print(f"Network error: {e}")
|
|
184
|
+
except APIStatusError as e:
|
|
185
|
+
print(f"Unexpected API error {e.status_code}: {e.response_body}")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Exception Hierarchy
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
SpeedAPIError
|
|
192
|
+
├── AuthenticationError # HTTP 401
|
|
193
|
+
├── PermissionDeniedError # HTTP 403
|
|
194
|
+
├── NotFoundError # HTTP 404
|
|
195
|
+
├── RateLimitError # HTTP 429
|
|
196
|
+
├── APIStatusError # other 4xx / 5xx (carries .status_code + .response_body)
|
|
197
|
+
└── APIConnectionError # network-level (timeout, DNS, etc.)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Webhook Verification
|
|
203
|
+
|
|
204
|
+
Speed signs every webhook with HMAC-SHA256. Always verify the signature before processing.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
208
|
+
from speedapi.resources.webhooks import Webhooks, WebhookSignatureVerificationError
|
|
209
|
+
|
|
210
|
+
app = FastAPI()
|
|
211
|
+
WEBHOOK_SECRET = "whsec_..." # from your Speed Dashboard
|
|
212
|
+
|
|
213
|
+
@app.post("/webhooks/speed")
|
|
214
|
+
async def speed_webhook(request: Request):
|
|
215
|
+
payload = await request.body()
|
|
216
|
+
sig_header = request.headers.get("Speed-Signature", "")
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
event = Webhooks.construct_event(
|
|
220
|
+
payload=payload,
|
|
221
|
+
sig_header=sig_header,
|
|
222
|
+
secret=WEBHOOK_SECRET,
|
|
223
|
+
)
|
|
224
|
+
except WebhookSignatureVerificationError as e:
|
|
225
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
226
|
+
|
|
227
|
+
if event.type == "checkout_session.completed":
|
|
228
|
+
session_id = event.data.get("object", {}).get("id")
|
|
229
|
+
print(f"✅ Checkout completed: {session_id}")
|
|
230
|
+
elif event.type == "invoice.paid":
|
|
231
|
+
invoice_id = event.data.get("object", {}).get("id")
|
|
232
|
+
print(f"💰 Invoice paid: {invoice_id}")
|
|
233
|
+
|
|
234
|
+
return {"received": True}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
> **Security**: The SDK automatically rejects webhooks older than 5 minutes to prevent replay attacks.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# Clone the repo
|
|
245
|
+
git clone https://github.com/furkankoykiran/speedapi_lib
|
|
246
|
+
cd speedapi_lib
|
|
247
|
+
|
|
248
|
+
# Install in editable mode with dev dependencies
|
|
249
|
+
pip install -e ".[dev]"
|
|
250
|
+
|
|
251
|
+
# Run the test suite
|
|
252
|
+
pytest tests/ -v --tb=short --cov=src/speedapi
|
|
253
|
+
|
|
254
|
+
# Type check
|
|
255
|
+
mypy src/speedapi
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
<div align="center">
|
|
267
|
+
Built with ❤️ by <a href="https://github.com/furkankoykiran">Furkan Köykıran</a>
|
|
268
|
+
</div>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "speedapi-lib"
|
|
7
|
+
version = "2.0.2"
|
|
8
|
+
description = "Python SDK for the Speed Merchant API — sync & async, Pydantic v2, full type hints"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Furkan Köykıran", email = "furkankoykiran@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["speed", "merchant", "api", "bitcoin", "lightning", "payments"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"httpx>=0.27",
|
|
29
|
+
"pydantic>=2.5",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"respx>=0.21",
|
|
37
|
+
"pytest-cov>=5.0",
|
|
38
|
+
"ruff>=0.4",
|
|
39
|
+
"mypy>=1.10",
|
|
40
|
+
"build>=1.2",
|
|
41
|
+
"twine>=5.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/furkankoykiran/speedapi_lib"
|
|
46
|
+
Documentation = "https://docs.tryspeed.com"
|
|
47
|
+
Repository = "https://github.com/furkankoykiran/speedapi_lib"
|
|
48
|
+
"Bug Tracker" = "https://github.com/furkankoykiran/speedapi_lib/issues"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
where = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
asyncio_mode = "auto"
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
addopts = "-v --tb=short"
|
|
57
|
+
|
|
58
|
+
[tool.coverage.run]
|
|
59
|
+
source = ["src/speedapi"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.9"
|
|
63
|
+
strict = true
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 100
|
|
67
|
+
target-version = "py39"
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = [
|
|
71
|
+
"E", # pycodestyle errors
|
|
72
|
+
"W", # pycodestyle warnings
|
|
73
|
+
"F", # pyflakes
|
|
74
|
+
"I", # isort
|
|
75
|
+
"B", # flake8-bugbear
|
|
76
|
+
"UP", # pyupgrade
|
|
77
|
+
]
|
|
78
|
+
ignore = [
|
|
79
|
+
"E501", # line too long — handled by formatter
|
|
80
|
+
"B008", # do not perform function calls in default arguments
|
|
81
|
+
"UP006", # use dict/list instead of Dict/List — we use __future__.annotations
|
|
82
|
+
"UP007", # use X | Y instead of Union — we use __future__.annotations
|
|
83
|
+
"UP035", # typing module deprecations — we use __future__.annotations
|
|
84
|
+
"UP045", # use X | None — we use __future__.annotations
|
|
85
|
+
"B904", # raise-without-from inside except — acceptable in SDK exception handling
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.per-file-ignores]
|
|
89
|
+
"tests/*" = ["S101"] # allow assert statements in tests
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[egg_info]
|
|
2
|
-
tag_build =
|
|
3
|
-
tag_date = 0
|
|
4
|
-
|
|
1
|
+
[egg_info]
|
|
2
|
+
tag_build =
|
|
3
|
+
tag_date = 0
|
|
4
|
+
|