zyndpay 1.0.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.
- zyndpay-1.0.0/PKG-INFO +328 -0
- zyndpay-1.0.0/README.md +303 -0
- zyndpay-1.0.0/setup.cfg +4 -0
- zyndpay-1.0.0/setup.py +22 -0
- zyndpay-1.0.0/src/zyndpay/__init__.py +21 -0
- zyndpay-1.0.0/src/zyndpay/client.py +60 -0
- zyndpay-1.0.0/src/zyndpay/errors.py +36 -0
- zyndpay-1.0.0/src/zyndpay/http_client.py +140 -0
- zyndpay-1.0.0/src/zyndpay/resources/__init__.py +6 -0
- zyndpay-1.0.0/src/zyndpay/resources/balances.py +10 -0
- zyndpay-1.0.0/src/zyndpay/resources/payins.py +74 -0
- zyndpay-1.0.0/src/zyndpay/resources/transactions.py +47 -0
- zyndpay-1.0.0/src/zyndpay/resources/withdrawals.py +56 -0
- zyndpay-1.0.0/src/zyndpay/webhooks.py +65 -0
- zyndpay-1.0.0/src/zyndpay.egg-info/PKG-INFO +328 -0
- zyndpay-1.0.0/src/zyndpay.egg-info/SOURCES.txt +17 -0
- zyndpay-1.0.0/src/zyndpay.egg-info/dependency_links.txt +1 -0
- zyndpay-1.0.0/src/zyndpay.egg-info/requires.txt +1 -0
- zyndpay-1.0.0/src/zyndpay.egg-info/top_level.txt +1 -0
zyndpay-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zyndpay
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official ZyndPay Python SDK — accept USDT payments with a few lines of code
|
|
5
|
+
Home-page: https://github.com/zyndpay/zyndpay-python
|
|
6
|
+
Author: ZyndPay
|
|
7
|
+
Author-email: dev@zyndpay.com
|
|
8
|
+
Keywords: zyndpay,crypto,usdt,trc20,payment-gateway
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: requests>=2.28.0
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: keywords
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
# zyndpay
|
|
27
|
+
|
|
28
|
+
Official ZyndPay Python SDK — accept USDT TRC20 payments with a few lines of code.
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/zyndpay/)
|
|
31
|
+
[](https://pypi.org/project/zyndpay/)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Python 3.8+
|
|
39
|
+
- A ZyndPay account and API key
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install zyndpay
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quickstart
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from zyndpay import ZyndPay
|
|
55
|
+
|
|
56
|
+
zyndpay = ZyndPay("zyp_live_sk_...")
|
|
57
|
+
|
|
58
|
+
# Create a payment request
|
|
59
|
+
payin = zyndpay.payins.create(amount="100")
|
|
60
|
+
print(payin["depositAddress"]) # Send USDT TRC20 here
|
|
61
|
+
print(payin["paymentPageUrl"]) # Redirect your customer here
|
|
62
|
+
|
|
63
|
+
# Check your balance
|
|
64
|
+
balance = zyndpay.balances.get()
|
|
65
|
+
print(balance["available"]) # e.g. "97.00"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
zyndpay = ZyndPay(
|
|
74
|
+
api_key="zyp_live_sk_...", # required
|
|
75
|
+
webhook_secret="whsec_...", # optional — needed for webhook verification
|
|
76
|
+
base_url="https://api.zyndpay.com/v1", # optional — override for self-hosted
|
|
77
|
+
timeout=30, # optional — seconds (default: 30)
|
|
78
|
+
max_retries=2, # optional — retries on network errors (default: 2)
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### API key types
|
|
83
|
+
|
|
84
|
+
| Prefix | Type |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `zyp_live_sk_` | Live secret key |
|
|
87
|
+
| `zyp_live_pk_` | Live publishable key |
|
|
88
|
+
| `zyp_test_sk_` | Sandbox secret key |
|
|
89
|
+
| `zyp_test_pk_` | Sandbox publishable key |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Payins
|
|
94
|
+
|
|
95
|
+
### Create a payin
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
payin = zyndpay.payins.create(
|
|
99
|
+
amount="100", # USDT amount (minimum 20)
|
|
100
|
+
external_ref="order_9f8e7d", # your internal order ID (optional)
|
|
101
|
+
expires_in_seconds=3600, # 1 hour — default is 24h (optional)
|
|
102
|
+
metadata={"user_id": "usr_123"}, # stored as-is (optional)
|
|
103
|
+
success_url="https://yoursite.com/success",
|
|
104
|
+
cancel_url="https://yoursite.com/cancel",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
print(payin["id"]) # "pay_..."
|
|
108
|
+
print(payin["depositAddress"]) # TRC20 address to receive payment
|
|
109
|
+
print(payin["amount"]) # "100"
|
|
110
|
+
print(payin["fee"]) # "3.00"
|
|
111
|
+
print(payin["netAmount"]) # "97.00"
|
|
112
|
+
print(payin["status"]) # "AWAITING_PAYMENT"
|
|
113
|
+
print(payin["expiresAt"]) # ISO timestamp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Get a payin
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
payin = zyndpay.payins.get("pay_abc123")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### List payins
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
result = zyndpay.payins.list(status="CONFIRMED", page=1, limit=20)
|
|
126
|
+
for payin in result["data"]:
|
|
127
|
+
print(payin["id"], payin["status"])
|
|
128
|
+
|
|
129
|
+
meta = result["meta"]
|
|
130
|
+
print(meta["total"], meta["totalPages"])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Payin statuses
|
|
134
|
+
|
|
135
|
+
| Status | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `PENDING` | Just created |
|
|
138
|
+
| `AWAITING_PAYMENT` | Deposit address assigned, waiting for funds |
|
|
139
|
+
| `CONFIRMING` | Payment detected, waiting for confirmations |
|
|
140
|
+
| `CONFIRMED` | Payment confirmed — balance credited |
|
|
141
|
+
| `EXPIRED` | Payment window elapsed |
|
|
142
|
+
| `OVERPAID` | More than expected was sent |
|
|
143
|
+
| `UNDERPAID` | Less than expected was sent |
|
|
144
|
+
| `FAILED` | Processing failed |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Sandbox / Test Mode
|
|
149
|
+
|
|
150
|
+
Use your sandbox API key (`zyp_test_sk_...`) and pass `sandbox=True` when creating a payin. Then call `simulate` to instantly confirm it without real funds.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
zyndpay = ZyndPay("zyp_test_sk_...")
|
|
154
|
+
|
|
155
|
+
# Create a sandbox payin
|
|
156
|
+
payin = zyndpay.payins.create(amount="100", sandbox=True)
|
|
157
|
+
|
|
158
|
+
# Instantly simulate confirmation
|
|
159
|
+
confirmed = zyndpay.payins.simulate(payin["id"])
|
|
160
|
+
print(confirmed["status"]) # "CONFIRMED"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Withdrawals
|
|
166
|
+
|
|
167
|
+
### Request a withdrawal
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
withdrawal = zyndpay.withdrawals.create(
|
|
171
|
+
amount="50", # USDT amount
|
|
172
|
+
idempotency_key="idempotency-key-123" # optional
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
print(withdrawal["status"]) # "PENDING_REVIEW"
|
|
176
|
+
print(withdrawal["fee"]) # "2.00" (flat $2 fee)
|
|
177
|
+
print(withdrawal["netAmount"]) # "48.00"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Get / list withdrawals
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
withdrawal = zyndpay.withdrawals.get("wdr_abc123")
|
|
184
|
+
|
|
185
|
+
result = zyndpay.withdrawals.list(status="CONFIRMED", page=1, limit=20)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Cancel a withdrawal
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
zyndpay.withdrawals.cancel("wdr_abc123") # only while PENDING_REVIEW
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Withdrawal statuses
|
|
195
|
+
|
|
196
|
+
| Status | Description |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `PENDING_REVIEW` | Awaiting admin approval |
|
|
199
|
+
| `APPROVED` | Approved, queued for processing |
|
|
200
|
+
| `PROCESSING` | Being broadcast to blockchain |
|
|
201
|
+
| `BROADCAST` | Transaction sent |
|
|
202
|
+
| `CONFIRMED` | On-chain confirmed |
|
|
203
|
+
| `REJECTED` | Rejected by admin |
|
|
204
|
+
| `CANCELLED` | Cancelled by merchant |
|
|
205
|
+
| `FAILED` | Broadcast failed |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Transactions
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# Get a single transaction
|
|
213
|
+
tx = zyndpay.transactions.get("txn_abc123")
|
|
214
|
+
|
|
215
|
+
# List with filters
|
|
216
|
+
result = zyndpay.transactions.list(
|
|
217
|
+
type="PAYIN", # "PAYIN" | "PAYOUT"
|
|
218
|
+
status="CONFIRMED",
|
|
219
|
+
from_date="2026-01-01",
|
|
220
|
+
to_date="2026-03-31",
|
|
221
|
+
page=1,
|
|
222
|
+
limit=50,
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Balances
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
balance = zyndpay.balances.get()
|
|
232
|
+
print(balance["currency"]) # "USDT_TRC20"
|
|
233
|
+
print(balance["available"]) # spendable balance
|
|
234
|
+
print(balance["pending"]) # in-flight / unconfirmed
|
|
235
|
+
print(balance["total"]) # available + pending
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Webhooks
|
|
241
|
+
|
|
242
|
+
ZyndPay sends signed webhook events to your endpoint. Always verify the signature before processing.
|
|
243
|
+
|
|
244
|
+
### Verify a webhook (Flask example)
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from flask import Flask, request, abort
|
|
248
|
+
from zyndpay import ZyndPay
|
|
249
|
+
|
|
250
|
+
zyndpay = ZyndPay(
|
|
251
|
+
api_key="zyp_live_sk_...",
|
|
252
|
+
webhook_secret="whsec_...",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
app = Flask(__name__)
|
|
256
|
+
|
|
257
|
+
@app.route("/webhooks/zyndpay", methods=["POST"])
|
|
258
|
+
def handle_webhook():
|
|
259
|
+
# IMPORTANT: use raw body — do not parse JSON before verifying
|
|
260
|
+
payload = request.get_data(as_text=True)
|
|
261
|
+
signature = request.headers.get("X-ZyndPay-Signature", "")
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
event = zyndpay.webhooks.verify(payload, signature)
|
|
265
|
+
except ValueError as e:
|
|
266
|
+
return str(e), 400
|
|
267
|
+
|
|
268
|
+
if event["type"] == "payin.confirmed":
|
|
269
|
+
print("Payment confirmed:", event["data"])
|
|
270
|
+
elif event["type"] == "withdrawal.confirmed":
|
|
271
|
+
print("Withdrawal confirmed:", event["data"])
|
|
272
|
+
|
|
273
|
+
return {"received": True}, 200
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Webhook event types
|
|
277
|
+
|
|
278
|
+
| Event | Trigger |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `payin.created` | Payin created |
|
|
281
|
+
| `payin.confirming` | Payment detected on-chain |
|
|
282
|
+
| `payin.confirmed` | Payment fully confirmed |
|
|
283
|
+
| `payin.expired` | Payin expired before payment |
|
|
284
|
+
| `payin.overpaid` | More than expected received |
|
|
285
|
+
| `payin.underpaid` | Less than expected received |
|
|
286
|
+
| `payin.failed` | Processing error |
|
|
287
|
+
| `withdrawal.requested` | Withdrawal created |
|
|
288
|
+
| `withdrawal.approved` | Approved by admin |
|
|
289
|
+
| `withdrawal.rejected` | Rejected by admin |
|
|
290
|
+
| `withdrawal.broadcast` | Sent to blockchain |
|
|
291
|
+
| `withdrawal.confirmed` | On-chain confirmed |
|
|
292
|
+
| `withdrawal.failed` | Broadcast failed |
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Error Handling
|
|
297
|
+
|
|
298
|
+
All SDK errors inherit from `ZyndPayError` and include `status_code` and an optional `request_id`.
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from zyndpay import (
|
|
302
|
+
ZyndPayError,
|
|
303
|
+
AuthenticationError,
|
|
304
|
+
ValidationError,
|
|
305
|
+
NotFoundError,
|
|
306
|
+
RateLimitError,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
payin = zyndpay.payins.create(amount="5") # below minimum
|
|
311
|
+
except ValidationError as e:
|
|
312
|
+
print("Bad request:", e) # "amount must be >= 20"
|
|
313
|
+
print("Status code:", e.status_code) # 400
|
|
314
|
+
except AuthenticationError:
|
|
315
|
+
print("Invalid API key")
|
|
316
|
+
except NotFoundError:
|
|
317
|
+
print("Resource not found")
|
|
318
|
+
except RateLimitError as e:
|
|
319
|
+
print(f"Rate limited, retry after {e.retry_after}s")
|
|
320
|
+
except ZyndPayError as e:
|
|
321
|
+
print(f"API error {e.status_code}: {e}")
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT — see [LICENSE](LICENSE)
|
zyndpay-1.0.0/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# zyndpay
|
|
2
|
+
|
|
3
|
+
Official ZyndPay Python SDK — accept USDT TRC20 payments with a few lines of code.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/zyndpay/)
|
|
6
|
+
[](https://pypi.org/project/zyndpay/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Python 3.8+
|
|
14
|
+
- A ZyndPay account and API key
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install zyndpay
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from zyndpay import ZyndPay
|
|
30
|
+
|
|
31
|
+
zyndpay = ZyndPay("zyp_live_sk_...")
|
|
32
|
+
|
|
33
|
+
# Create a payment request
|
|
34
|
+
payin = zyndpay.payins.create(amount="100")
|
|
35
|
+
print(payin["depositAddress"]) # Send USDT TRC20 here
|
|
36
|
+
print(payin["paymentPageUrl"]) # Redirect your customer here
|
|
37
|
+
|
|
38
|
+
# Check your balance
|
|
39
|
+
balance = zyndpay.balances.get()
|
|
40
|
+
print(balance["available"]) # e.g. "97.00"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
zyndpay = ZyndPay(
|
|
49
|
+
api_key="zyp_live_sk_...", # required
|
|
50
|
+
webhook_secret="whsec_...", # optional — needed for webhook verification
|
|
51
|
+
base_url="https://api.zyndpay.com/v1", # optional — override for self-hosted
|
|
52
|
+
timeout=30, # optional — seconds (default: 30)
|
|
53
|
+
max_retries=2, # optional — retries on network errors (default: 2)
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### API key types
|
|
58
|
+
|
|
59
|
+
| Prefix | Type |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `zyp_live_sk_` | Live secret key |
|
|
62
|
+
| `zyp_live_pk_` | Live publishable key |
|
|
63
|
+
| `zyp_test_sk_` | Sandbox secret key |
|
|
64
|
+
| `zyp_test_pk_` | Sandbox publishable key |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Payins
|
|
69
|
+
|
|
70
|
+
### Create a payin
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
payin = zyndpay.payins.create(
|
|
74
|
+
amount="100", # USDT amount (minimum 20)
|
|
75
|
+
external_ref="order_9f8e7d", # your internal order ID (optional)
|
|
76
|
+
expires_in_seconds=3600, # 1 hour — default is 24h (optional)
|
|
77
|
+
metadata={"user_id": "usr_123"}, # stored as-is (optional)
|
|
78
|
+
success_url="https://yoursite.com/success",
|
|
79
|
+
cancel_url="https://yoursite.com/cancel",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
print(payin["id"]) # "pay_..."
|
|
83
|
+
print(payin["depositAddress"]) # TRC20 address to receive payment
|
|
84
|
+
print(payin["amount"]) # "100"
|
|
85
|
+
print(payin["fee"]) # "3.00"
|
|
86
|
+
print(payin["netAmount"]) # "97.00"
|
|
87
|
+
print(payin["status"]) # "AWAITING_PAYMENT"
|
|
88
|
+
print(payin["expiresAt"]) # ISO timestamp
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Get a payin
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
payin = zyndpay.payins.get("pay_abc123")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### List payins
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
result = zyndpay.payins.list(status="CONFIRMED", page=1, limit=20)
|
|
101
|
+
for payin in result["data"]:
|
|
102
|
+
print(payin["id"], payin["status"])
|
|
103
|
+
|
|
104
|
+
meta = result["meta"]
|
|
105
|
+
print(meta["total"], meta["totalPages"])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Payin statuses
|
|
109
|
+
|
|
110
|
+
| Status | Description |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `PENDING` | Just created |
|
|
113
|
+
| `AWAITING_PAYMENT` | Deposit address assigned, waiting for funds |
|
|
114
|
+
| `CONFIRMING` | Payment detected, waiting for confirmations |
|
|
115
|
+
| `CONFIRMED` | Payment confirmed — balance credited |
|
|
116
|
+
| `EXPIRED` | Payment window elapsed |
|
|
117
|
+
| `OVERPAID` | More than expected was sent |
|
|
118
|
+
| `UNDERPAID` | Less than expected was sent |
|
|
119
|
+
| `FAILED` | Processing failed |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Sandbox / Test Mode
|
|
124
|
+
|
|
125
|
+
Use your sandbox API key (`zyp_test_sk_...`) and pass `sandbox=True` when creating a payin. Then call `simulate` to instantly confirm it without real funds.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
zyndpay = ZyndPay("zyp_test_sk_...")
|
|
129
|
+
|
|
130
|
+
# Create a sandbox payin
|
|
131
|
+
payin = zyndpay.payins.create(amount="100", sandbox=True)
|
|
132
|
+
|
|
133
|
+
# Instantly simulate confirmation
|
|
134
|
+
confirmed = zyndpay.payins.simulate(payin["id"])
|
|
135
|
+
print(confirmed["status"]) # "CONFIRMED"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Withdrawals
|
|
141
|
+
|
|
142
|
+
### Request a withdrawal
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
withdrawal = zyndpay.withdrawals.create(
|
|
146
|
+
amount="50", # USDT amount
|
|
147
|
+
idempotency_key="idempotency-key-123" # optional
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
print(withdrawal["status"]) # "PENDING_REVIEW"
|
|
151
|
+
print(withdrawal["fee"]) # "2.00" (flat $2 fee)
|
|
152
|
+
print(withdrawal["netAmount"]) # "48.00"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Get / list withdrawals
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
withdrawal = zyndpay.withdrawals.get("wdr_abc123")
|
|
159
|
+
|
|
160
|
+
result = zyndpay.withdrawals.list(status="CONFIRMED", page=1, limit=20)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Cancel a withdrawal
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
zyndpay.withdrawals.cancel("wdr_abc123") # only while PENDING_REVIEW
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Withdrawal statuses
|
|
170
|
+
|
|
171
|
+
| Status | Description |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `PENDING_REVIEW` | Awaiting admin approval |
|
|
174
|
+
| `APPROVED` | Approved, queued for processing |
|
|
175
|
+
| `PROCESSING` | Being broadcast to blockchain |
|
|
176
|
+
| `BROADCAST` | Transaction sent |
|
|
177
|
+
| `CONFIRMED` | On-chain confirmed |
|
|
178
|
+
| `REJECTED` | Rejected by admin |
|
|
179
|
+
| `CANCELLED` | Cancelled by merchant |
|
|
180
|
+
| `FAILED` | Broadcast failed |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Transactions
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# Get a single transaction
|
|
188
|
+
tx = zyndpay.transactions.get("txn_abc123")
|
|
189
|
+
|
|
190
|
+
# List with filters
|
|
191
|
+
result = zyndpay.transactions.list(
|
|
192
|
+
type="PAYIN", # "PAYIN" | "PAYOUT"
|
|
193
|
+
status="CONFIRMED",
|
|
194
|
+
from_date="2026-01-01",
|
|
195
|
+
to_date="2026-03-31",
|
|
196
|
+
page=1,
|
|
197
|
+
limit=50,
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Balances
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
balance = zyndpay.balances.get()
|
|
207
|
+
print(balance["currency"]) # "USDT_TRC20"
|
|
208
|
+
print(balance["available"]) # spendable balance
|
|
209
|
+
print(balance["pending"]) # in-flight / unconfirmed
|
|
210
|
+
print(balance["total"]) # available + pending
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Webhooks
|
|
216
|
+
|
|
217
|
+
ZyndPay sends signed webhook events to your endpoint. Always verify the signature before processing.
|
|
218
|
+
|
|
219
|
+
### Verify a webhook (Flask example)
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from flask import Flask, request, abort
|
|
223
|
+
from zyndpay import ZyndPay
|
|
224
|
+
|
|
225
|
+
zyndpay = ZyndPay(
|
|
226
|
+
api_key="zyp_live_sk_...",
|
|
227
|
+
webhook_secret="whsec_...",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
app = Flask(__name__)
|
|
231
|
+
|
|
232
|
+
@app.route("/webhooks/zyndpay", methods=["POST"])
|
|
233
|
+
def handle_webhook():
|
|
234
|
+
# IMPORTANT: use raw body — do not parse JSON before verifying
|
|
235
|
+
payload = request.get_data(as_text=True)
|
|
236
|
+
signature = request.headers.get("X-ZyndPay-Signature", "")
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
event = zyndpay.webhooks.verify(payload, signature)
|
|
240
|
+
except ValueError as e:
|
|
241
|
+
return str(e), 400
|
|
242
|
+
|
|
243
|
+
if event["type"] == "payin.confirmed":
|
|
244
|
+
print("Payment confirmed:", event["data"])
|
|
245
|
+
elif event["type"] == "withdrawal.confirmed":
|
|
246
|
+
print("Withdrawal confirmed:", event["data"])
|
|
247
|
+
|
|
248
|
+
return {"received": True}, 200
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Webhook event types
|
|
252
|
+
|
|
253
|
+
| Event | Trigger |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `payin.created` | Payin created |
|
|
256
|
+
| `payin.confirming` | Payment detected on-chain |
|
|
257
|
+
| `payin.confirmed` | Payment fully confirmed |
|
|
258
|
+
| `payin.expired` | Payin expired before payment |
|
|
259
|
+
| `payin.overpaid` | More than expected received |
|
|
260
|
+
| `payin.underpaid` | Less than expected received |
|
|
261
|
+
| `payin.failed` | Processing error |
|
|
262
|
+
| `withdrawal.requested` | Withdrawal created |
|
|
263
|
+
| `withdrawal.approved` | Approved by admin |
|
|
264
|
+
| `withdrawal.rejected` | Rejected by admin |
|
|
265
|
+
| `withdrawal.broadcast` | Sent to blockchain |
|
|
266
|
+
| `withdrawal.confirmed` | On-chain confirmed |
|
|
267
|
+
| `withdrawal.failed` | Broadcast failed |
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Error Handling
|
|
272
|
+
|
|
273
|
+
All SDK errors inherit from `ZyndPayError` and include `status_code` and an optional `request_id`.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
from zyndpay import (
|
|
277
|
+
ZyndPayError,
|
|
278
|
+
AuthenticationError,
|
|
279
|
+
ValidationError,
|
|
280
|
+
NotFoundError,
|
|
281
|
+
RateLimitError,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
payin = zyndpay.payins.create(amount="5") # below minimum
|
|
286
|
+
except ValidationError as e:
|
|
287
|
+
print("Bad request:", e) # "amount must be >= 20"
|
|
288
|
+
print("Status code:", e.status_code) # 400
|
|
289
|
+
except AuthenticationError:
|
|
290
|
+
print("Invalid API key")
|
|
291
|
+
except NotFoundError:
|
|
292
|
+
print("Resource not found")
|
|
293
|
+
except RateLimitError as e:
|
|
294
|
+
print(f"Rate limited, retry after {e.retry_after}s")
|
|
295
|
+
except ZyndPayError as e:
|
|
296
|
+
print(f"API error {e.status_code}: {e}")
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT — see [LICENSE](LICENSE)
|
zyndpay-1.0.0/setup.cfg
ADDED
zyndpay-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="zyndpay",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
description="Official ZyndPay Python SDK — accept USDT payments with a few lines of code",
|
|
7
|
+
long_description=open("README.md").read() if __import__("os").path.exists("README.md") else "",
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="ZyndPay",
|
|
10
|
+
author_email="dev@zyndpay.com",
|
|
11
|
+
url="https://github.com/zyndpay/zyndpay-python",
|
|
12
|
+
packages=find_packages(where="src"),
|
|
13
|
+
package_dir={"": "src"},
|
|
14
|
+
python_requires=">=3.8",
|
|
15
|
+
install_requires=["requests>=2.28.0"],
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
],
|
|
21
|
+
keywords=["zyndpay", "crypto", "usdt", "trc20", "payment-gateway"],
|
|
22
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from zyndpay.client import ZyndPay
|
|
2
|
+
from zyndpay.errors import (
|
|
3
|
+
ZyndPayError,
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
ValidationError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
)
|
|
9
|
+
from zyndpay.webhooks import WebhookVerifier
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ZyndPay",
|
|
13
|
+
"WebhookVerifier",
|
|
14
|
+
"ZyndPayError",
|
|
15
|
+
"AuthenticationError",
|
|
16
|
+
"ValidationError",
|
|
17
|
+
"NotFoundError",
|
|
18
|
+
"RateLimitError",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
__version__ = "1.0.0"
|