-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathflow_payments.py
More file actions
190 lines (165 loc) · 6.46 KB
/
Copy pathflow_payments.py
File metadata and controls
190 lines (165 loc) · 6.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"""
Flow Payment Integration — Drop-in crypto payments for Telegram bots.
Usage:
from flow_payments import FlowPayments
flow = FlowPayments(api_key="fl_...", api_secret="your_secret")
invoice = await flow.create_invoice(amount=25.00, network="bsc")
# Send invoice["payment_url"] to your user
"""
import hashlib
import hmac
import logging
from decimal import Decimal
from typing import Optional
import aiohttp
logger = logging.getLogger(__name__)
FLOW_API_URL = "https://flow.vylth.com/api/flow"
class FlowPayments:
"""Async client for the Flow crypto payment API."""
def __init__(
self,
api_key: str,
api_secret: str,
vendor_key: Optional[str] = None,
vendor_secret: Optional[str] = None,
webhook_secret: Optional[str] = None,
api_url: str = FLOW_API_URL,
):
self.api_key = api_key
self.api_secret = api_secret
self.vendor_key = vendor_key
self.vendor_secret = vendor_secret
self.webhook_secret = webhook_secret
self.api_url = api_url.rstrip("/")
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession()
return self._session
async def close(self):
if self._session and not self._session.closed:
await self._session.close()
# ── Invoices (Deposits) ──────────────────────────────────────────
async def create_invoice(
self,
amount: float,
network: str = "bsc",
currency: str = "USDT",
customer_name: str = "",
customer_email: str = "",
metadata: Optional[dict] = None,
callback_url: Optional[str] = None,
) -> dict:
"""
Create a payment invoice. Returns dict with:
- id: invoice ID
- payment_url: URL to send to the customer
- deposit_address: crypto address
- crypto_amount: exact amount to send
- expires_at: ISO timestamp
"""
session = await self._get_session()
payload = {
"amount": float(Decimal(str(amount)).quantize(Decimal("0.01"))),
"crypto_currency": currency.upper(),
"network": network.lower(),
"customer_name": customer_name,
}
if customer_email:
payload["customer_email"] = customer_email
if metadata:
payload["metadata"] = metadata
if callback_url:
payload["callback_url"] = callback_url
async with session.post(
f"{self.api_url}/invoices/",
headers={
"X-API-Key": self.api_key,
"X-API-Secret": self.api_secret,
"Content-Type": "application/json",
},
json=payload,
) as resp:
data = await resp.json()
if resp.status >= 400:
raise FlowAPIError(resp.status, data)
return data
async def get_invoice(self, invoice_id: str) -> dict:
"""Get invoice status. Check data["status"] for: pending, confirming, completed, expired."""
session = await self._get_session()
async with session.get(
f"{self.api_url}/invoices/{invoice_id}",
headers={
"X-API-Key": self.api_key,
"X-API-Secret": self.api_secret,
},
) as resp:
data = await resp.json()
if resp.status >= 400:
raise FlowAPIError(resp.status, data)
return data
# ── Payouts (Withdrawals) ────────────────────────────────────────
async def create_payout(
self,
amount: float,
recipient_address: str,
network: str = "bsc",
currency: str = "USDT",
reference_id: str = "",
recipient_name: str = "",
note: str = "",
) -> dict:
"""
Send crypto to an address. Requires vendor keys.
Returns dict with payout_id.
"""
if not self.vendor_key or not self.vendor_secret:
raise FlowAPIError(0, {"error": "Vendor keys required for payouts"})
session = await self._get_session()
payload = {
"amount": float(Decimal(str(amount)).quantize(Decimal("0.01"))),
"currency": currency.upper(),
"network": network.lower(),
"recipient_address": recipient_address,
}
if reference_id:
payload["reference_id"] = reference_id
if recipient_name:
payload["recipient_name"] = recipient_name
if note:
payload["note"] = note
async with session.post(
f"{self.api_url}/vendor/payout",
headers={
"X-Vendor-Key": self.vendor_key,
"X-Vendor-Secret": self.vendor_secret,
"Content-Type": "application/json",
},
json=payload,
) as resp:
data = await resp.json()
if resp.status >= 400:
raise FlowAPIError(resp.status, data)
return data
# ── Webhook Verification ─────────────────────────────────────────
def verify_webhook(self, raw_body: bytes, signature: str) -> bool:
"""
Verify a Flow webhook signature.
Pass the raw request body (bytes) and the X-Flow-Signature header value.
"""
if not self.webhook_secret:
logger.warning("No webhook_secret configured — skipping verification")
return True
expected = hmac.new(
self.webhook_secret.encode(),
raw_body,
hashlib.sha256,
).hexdigest()
# Header may be "sha256=<hex>" or just "<hex>"
sig = signature.replace("sha256=", "")
return hmac.compare_digest(expected, sig)
class FlowAPIError(Exception):
def __init__(self, status: int, data: dict):
self.status = status
self.data = data
super().__init__(f"Flow API error {status}: {data}")