paydeck 1.0.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.
- paydeck/__init__.py +5 -0
- paydeck/client.py +322 -0
- paydeck/exceptions.py +18 -0
- paydeck-1.0.0.dist-info/METADATA +84 -0
- paydeck-1.0.0.dist-info/RECORD +7 -0
- paydeck-1.0.0.dist-info/WHEEL +5 -0
- paydeck-1.0.0.dist-info/top_level.txt +1 -0
paydeck/__init__.py
ADDED
paydeck/client.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import hmac
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
7
|
+
import urllib.parse
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
from paydeck.exceptions import PayDeckAPIError, PayDeckError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PayDeckClient:
|
|
14
|
+
"""Official Python SDK client for PayDeck billing microservice."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
base_url: str,
|
|
19
|
+
api_key: Optional[str] = None,
|
|
20
|
+
admin_token: Optional[str] = None,
|
|
21
|
+
timeout: float = 30.0,
|
|
22
|
+
retries: int = 3,
|
|
23
|
+
):
|
|
24
|
+
self.base_url = base_url.rstrip("/")
|
|
25
|
+
self.api_key = api_key
|
|
26
|
+
self.admin_token = admin_token
|
|
27
|
+
self.timeout = timeout
|
|
28
|
+
self.retries = retries
|
|
29
|
+
|
|
30
|
+
def _headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
|
|
31
|
+
headers = {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"User-Agent": "PayDeck-Python-SDK/1.0.0",
|
|
34
|
+
}
|
|
35
|
+
if self.api_key:
|
|
36
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
37
|
+
if self.admin_token:
|
|
38
|
+
headers["X-Admin-Token"] = self.admin_token
|
|
39
|
+
if idempotency_key:
|
|
40
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
41
|
+
return headers
|
|
42
|
+
|
|
43
|
+
def _request(
|
|
44
|
+
self,
|
|
45
|
+
method: str,
|
|
46
|
+
path: str,
|
|
47
|
+
params: Optional[Dict[str, Any]] = None,
|
|
48
|
+
data: Optional[Dict[str, Any]] = None,
|
|
49
|
+
idempotency_key: Optional[str] = None,
|
|
50
|
+
) -> Dict[str, Any]:
|
|
51
|
+
url = f"{self.base_url}{path}"
|
|
52
|
+
if params:
|
|
53
|
+
query = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
|
|
54
|
+
if query:
|
|
55
|
+
url = f"{url}?{query}"
|
|
56
|
+
|
|
57
|
+
body_bytes = None
|
|
58
|
+
if data is not None:
|
|
59
|
+
body_bytes = json.dumps(data).encode("utf-8")
|
|
60
|
+
|
|
61
|
+
headers = self._headers(idempotency_key=idempotency_key)
|
|
62
|
+
attempt = 0
|
|
63
|
+
|
|
64
|
+
while True:
|
|
65
|
+
attempt += 1
|
|
66
|
+
req = urllib.request.Request(url, data=body_bytes, headers=headers, method=method.upper())
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
70
|
+
resp_data = resp.read().decode("utf-8")
|
|
71
|
+
return json.loads(resp_data) if resp_data else {}
|
|
72
|
+
|
|
73
|
+
except urllib.error.HTTPError as e:
|
|
74
|
+
resp_data = e.read().decode("utf-8") if e.fp else ""
|
|
75
|
+
parsed_error = {}
|
|
76
|
+
try:
|
|
77
|
+
parsed_error = json.loads(resp_data) if resp_data else {}
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
# Extract standard error envelope fields
|
|
82
|
+
err_obj = parsed_error.get("error", {})
|
|
83
|
+
if isinstance(err_obj, str):
|
|
84
|
+
error_msg = err_obj
|
|
85
|
+
error_code = "API_ERROR"
|
|
86
|
+
details = []
|
|
87
|
+
request_id = parsed_error.get("request_id")
|
|
88
|
+
elif isinstance(err_obj, dict):
|
|
89
|
+
error_msg = err_obj.get("message") or err_obj.get("type") or "API Error"
|
|
90
|
+
error_code = err_obj.get("code") or err_obj.get("type") or "API_ERROR"
|
|
91
|
+
details = err_obj.get("details", [])
|
|
92
|
+
request_id = err_obj.get("request_id") or parsed_error.get("request_id")
|
|
93
|
+
else:
|
|
94
|
+
error_msg = e.reason or str(e)
|
|
95
|
+
error_code = f"HTTP_{e.code}"
|
|
96
|
+
details = []
|
|
97
|
+
request_id = None
|
|
98
|
+
|
|
99
|
+
# Retry on 429 rate limit or 5xx server error
|
|
100
|
+
if e.code in (429, 500, 502, 503, 504) and attempt <= self.retries:
|
|
101
|
+
delay = 0.1 * (2 ** (attempt - 1))
|
|
102
|
+
time.sleep(delay)
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
raise PayDeckAPIError(
|
|
106
|
+
message=error_msg,
|
|
107
|
+
status_code=e.code,
|
|
108
|
+
error_code=error_code,
|
|
109
|
+
details=details,
|
|
110
|
+
request_id=request_id,
|
|
111
|
+
) from e
|
|
112
|
+
|
|
113
|
+
except (urllib.error.URLError, TimeoutError, ConnectionError) as e:
|
|
114
|
+
if attempt <= self.retries:
|
|
115
|
+
delay = 0.1 * (2 ** (attempt - 1))
|
|
116
|
+
time.sleep(delay)
|
|
117
|
+
continue
|
|
118
|
+
raise PayDeckError(f"Network error communicating with PayDeck: {e}") from e
|
|
119
|
+
|
|
120
|
+
# --- Consumer Billing Methods ---
|
|
121
|
+
|
|
122
|
+
def create_order(
|
|
123
|
+
self,
|
|
124
|
+
plan: Optional[str] = None,
|
|
125
|
+
amount_paise: Optional[int] = None,
|
|
126
|
+
currency: str = "INR",
|
|
127
|
+
receipt: Optional[str] = None,
|
|
128
|
+
notes: Optional[Dict[str, str]] = None,
|
|
129
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
130
|
+
description: Optional[str] = None,
|
|
131
|
+
idempotency_key: Optional[str] = None,
|
|
132
|
+
customer_id: Optional[str] = None,
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
payload: Dict[str, Any] = {"currency": currency}
|
|
135
|
+
if plan:
|
|
136
|
+
payload["plan"] = plan
|
|
137
|
+
if amount_paise is not None:
|
|
138
|
+
payload["amount_paise"] = amount_paise
|
|
139
|
+
if receipt:
|
|
140
|
+
payload["receipt"] = receipt
|
|
141
|
+
if notes:
|
|
142
|
+
payload["notes"] = notes
|
|
143
|
+
if metadata:
|
|
144
|
+
payload["metadata"] = metadata
|
|
145
|
+
if description:
|
|
146
|
+
payload["description"] = description
|
|
147
|
+
if customer_id:
|
|
148
|
+
payload["customer_id"] = customer_id
|
|
149
|
+
|
|
150
|
+
return self._request("POST", "/v1/orders", data=payload, idempotency_key=idempotency_key)
|
|
151
|
+
|
|
152
|
+
def verify(
|
|
153
|
+
self,
|
|
154
|
+
razorpay_order_id: str,
|
|
155
|
+
razorpay_payment_id: str,
|
|
156
|
+
razorpay_signature: str,
|
|
157
|
+
) -> Dict[str, Any]:
|
|
158
|
+
payload = {
|
|
159
|
+
"razorpay_order_id": razorpay_order_id,
|
|
160
|
+
"razorpay_payment_id": razorpay_payment_id,
|
|
161
|
+
"razorpay_signature": razorpay_signature,
|
|
162
|
+
}
|
|
163
|
+
return self._request("POST", "/v1/verify", data=payload)
|
|
164
|
+
|
|
165
|
+
def get_payment(self, payment_id: str) -> Dict[str, Any]:
|
|
166
|
+
return self._request("GET", f"/v1/payments/{urllib.parse.quote(payment_id)}")
|
|
167
|
+
|
|
168
|
+
def list_payments(self, limit: int = 25, offset: int = 0) -> Dict[str, Any]:
|
|
169
|
+
return self._request("GET", "/v1/payments", params={"limit": limit, "offset": offset})
|
|
170
|
+
|
|
171
|
+
def list_plans(self) -> Dict[str, Any]:
|
|
172
|
+
return self._request("GET", "/v1/plans")
|
|
173
|
+
|
|
174
|
+
def refund_payment(
|
|
175
|
+
self,
|
|
176
|
+
payment_id: str,
|
|
177
|
+
amount_paise: Optional[int] = None,
|
|
178
|
+
reason: Optional[str] = None,
|
|
179
|
+
) -> Dict[str, Any]:
|
|
180
|
+
payload: Dict[str, Any] = {}
|
|
181
|
+
if amount_paise is not None:
|
|
182
|
+
payload["amount_paise"] = amount_paise
|
|
183
|
+
if reason:
|
|
184
|
+
payload["reason"] = reason
|
|
185
|
+
return self._request("POST", f"/v1/payments/{urllib.parse.quote(payment_id)}/refund", data=payload)
|
|
186
|
+
|
|
187
|
+
# --- Customer Domain Methods ---
|
|
188
|
+
|
|
189
|
+
def create_customer(
|
|
190
|
+
self,
|
|
191
|
+
email: Optional[str] = None,
|
|
192
|
+
name: Optional[str] = None,
|
|
193
|
+
external_user_id: Optional[str] = None,
|
|
194
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
195
|
+
) -> Dict[str, Any]:
|
|
196
|
+
payload: Dict[str, Any] = {}
|
|
197
|
+
if email:
|
|
198
|
+
payload["email"] = email
|
|
199
|
+
if name:
|
|
200
|
+
payload["name"] = name
|
|
201
|
+
if external_user_id:
|
|
202
|
+
payload["external_user_id"] = external_user_id
|
|
203
|
+
if metadata:
|
|
204
|
+
payload["metadata"] = metadata
|
|
205
|
+
return self._request("POST", "/v1/customers", data=payload)
|
|
206
|
+
|
|
207
|
+
def list_customers(self, limit: int = 25, offset: int = 0) -> Dict[str, Any]:
|
|
208
|
+
return self._request("GET", "/v1/customers", params={"limit": limit, "offset": offset})
|
|
209
|
+
|
|
210
|
+
def list_customer_payments(self, customer_id: str, limit: int = 25, offset: int = 0) -> Dict[str, Any]:
|
|
211
|
+
return self._request("GET", f"/v1/customers/{urllib.parse.quote(customer_id)}/payments", params={"limit": limit, "offset": offset})
|
|
212
|
+
|
|
213
|
+
# --- Subscription Methods ---
|
|
214
|
+
|
|
215
|
+
def create_subscription(
|
|
216
|
+
self,
|
|
217
|
+
customer_id: str,
|
|
218
|
+
plan_id: str,
|
|
219
|
+
period_days: int = 30,
|
|
220
|
+
) -> Dict[str, Any]:
|
|
221
|
+
payload = {
|
|
222
|
+
"customer_id": customer_id,
|
|
223
|
+
"plan_id": plan_id,
|
|
224
|
+
"period_days": period_days,
|
|
225
|
+
}
|
|
226
|
+
return self._request("POST", "/v1/subscriptions", data=payload)
|
|
227
|
+
|
|
228
|
+
def list_subscriptions(self, limit: int = 25, offset: int = 0, customer_id: Optional[str] = None) -> Dict[str, Any]:
|
|
229
|
+
params = {"limit": limit, "offset": offset}
|
|
230
|
+
if customer_id:
|
|
231
|
+
params["customer_id"] = customer_id
|
|
232
|
+
return self._request("GET", "/v1/subscriptions", params=params)
|
|
233
|
+
|
|
234
|
+
def cancel_subscription(self, subscription_id: str, immediately: bool = True) -> Dict[str, Any]:
|
|
235
|
+
return self._request("POST", f"/v1/subscriptions/{urllib.parse.quote(subscription_id)}/cancel", data={"immediately": immediately})
|
|
236
|
+
|
|
237
|
+
# --- Admin Methods ---
|
|
238
|
+
|
|
239
|
+
def create_product(self, slug: str, name: str, rate_limit_per_hour: int = 200) -> Dict[str, Any]:
|
|
240
|
+
payload = {"slug": slug, "name": name, "rate_limit_per_hour": rate_limit_per_hour}
|
|
241
|
+
return self._request("POST", "/v1/admin/products", data=payload)
|
|
242
|
+
|
|
243
|
+
def list_products(self) -> Dict[str, Any]:
|
|
244
|
+
return self._request("GET", "/v1/admin/products")
|
|
245
|
+
|
|
246
|
+
def mint_key(self, slug: str, name: Optional[str] = None, environment: str = "live") -> Dict[str, Any]:
|
|
247
|
+
payload: Dict[str, Any] = {"environment": environment}
|
|
248
|
+
if name:
|
|
249
|
+
payload["name"] = name
|
|
250
|
+
return self._request("POST", f"/v1/admin/products/{urllib.parse.quote(slug)}/keys", data=payload)
|
|
251
|
+
|
|
252
|
+
def revoke_key(self, slug: str, key_id: str) -> Dict[str, Any]:
|
|
253
|
+
return self._request("POST", f"/v1/admin/products/{urllib.parse.quote(slug)}/keys/{urllib.parse.quote(key_id)}/revoke")
|
|
254
|
+
|
|
255
|
+
def create_plan(
|
|
256
|
+
self,
|
|
257
|
+
slug: str,
|
|
258
|
+
plan_slug: str,
|
|
259
|
+
name: str,
|
|
260
|
+
amount_paise: int,
|
|
261
|
+
currency: str = "INR",
|
|
262
|
+
interval: str = "month",
|
|
263
|
+
) -> Dict[str, Any]:
|
|
264
|
+
payload = {
|
|
265
|
+
"slug": plan_slug,
|
|
266
|
+
"name": name,
|
|
267
|
+
"amount_paise": amount_paise,
|
|
268
|
+
"currency": currency,
|
|
269
|
+
"interval": interval,
|
|
270
|
+
}
|
|
271
|
+
return self._request("POST", f"/v1/admin/products/{urllib.parse.quote(slug)}/plans", data=payload)
|
|
272
|
+
|
|
273
|
+
def list_audit_logs(
|
|
274
|
+
self,
|
|
275
|
+
limit: int = 25,
|
|
276
|
+
offset: int = 0,
|
|
277
|
+
product_id: Optional[str] = None,
|
|
278
|
+
action: Optional[str] = None,
|
|
279
|
+
) -> Dict[str, Any]:
|
|
280
|
+
params: Dict[str, Any] = {"limit": limit, "offset": offset}
|
|
281
|
+
if product_id:
|
|
282
|
+
params["product_id"] = product_id
|
|
283
|
+
if action:
|
|
284
|
+
params["action"] = action
|
|
285
|
+
return self._request("GET", "/v1/admin/audit-logs", params=params)
|
|
286
|
+
|
|
287
|
+
def list_webhooks(
|
|
288
|
+
self,
|
|
289
|
+
limit: int = 25,
|
|
290
|
+
offset: int = 0,
|
|
291
|
+
status: Optional[str] = None,
|
|
292
|
+
product_id: Optional[str] = None,
|
|
293
|
+
) -> Dict[str, Any]:
|
|
294
|
+
params: Dict[str, Any] = {"limit": limit, "offset": offset}
|
|
295
|
+
if status:
|
|
296
|
+
params["status"] = status
|
|
297
|
+
if product_id:
|
|
298
|
+
params["product_id"] = product_id
|
|
299
|
+
return self._request("GET", "/v1/admin/webhooks", params=params)
|
|
300
|
+
|
|
301
|
+
def redeliver_webhook(self, delivery_id: str) -> Dict[str, Any]:
|
|
302
|
+
return self._request("POST", f"/v1/admin/webhooks/{urllib.parse.quote(delivery_id)}/redeliver")
|
|
303
|
+
|
|
304
|
+
# --- Static Helpers ---
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def verify_webhook_signature(
|
|
308
|
+
payload: Union[str, bytes],
|
|
309
|
+
signature: str,
|
|
310
|
+
secret: str,
|
|
311
|
+
) -> bool:
|
|
312
|
+
"""Statically verify downstream HMAC-SHA256 PayDeck webhook signatures."""
|
|
313
|
+
if not signature or not secret:
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
if isinstance(payload, str):
|
|
317
|
+
payload_bytes = payload.encode("utf-8")
|
|
318
|
+
else:
|
|
319
|
+
payload_bytes = payload
|
|
320
|
+
|
|
321
|
+
expected_sig = hmac.new(secret.encode("utf-8"), payload_bytes, hashlib.sha256).hexdigest()
|
|
322
|
+
return hmac.compare_digest(expected_sig.lower(), signature.strip().lower())
|
paydeck/exceptions.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class PayDeckError(Exception):
|
|
2
|
+
"""Base exception for all PayDeck SDK errors."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PayDeckAPIError(PayDeckError):
|
|
7
|
+
"""Raised when the PayDeck API returns an error response."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, status_code: int, error_code: str = None, details: list = None, request_id: str = None):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.message = message
|
|
12
|
+
self.status_code = status_code
|
|
13
|
+
self.error_code = error_code or "API_ERROR"
|
|
14
|
+
self.details = details or []
|
|
15
|
+
self.request_id = request_id
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f"PayDeckAPIError(status_code={self.status_code}, code={self.error_code!r}, message={self.message!r}, request_id={self.request_id!r})"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paydeck
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for PayDeck billing microservice
|
|
5
|
+
Author-email: PiSigma / PayDeck Team <dev@pisigma.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pisigmac/PayDeck
|
|
8
|
+
Project-URL: Repository, https://github.com/pisigmac/PayDeck.git
|
|
9
|
+
Keywords: paydeck,billing,razorpay,stripe,payments,sdk
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# PayDeck Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for the [PayDeck](https://github.com/pisigmac/PayDeck) generic billing microservice. Zero third-party runtime dependencies (uses standard library `urllib`, `json`, `hmac`, `hashlib`).
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install paydeck
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from paydeck import PayDeckClient, PayDeckAPIError
|
|
35
|
+
|
|
36
|
+
# Initialize client with product API key
|
|
37
|
+
client = PayDeckClient(
|
|
38
|
+
base_url="http://localhost:8787",
|
|
39
|
+
api_key="pd_live_1234567890abcdef...",
|
|
40
|
+
retries=3,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# 1. Create an order for a plan or custom amount
|
|
44
|
+
order = client.create_order(
|
|
45
|
+
plan="pro",
|
|
46
|
+
idempotency_key="unique_checkout_session_456"
|
|
47
|
+
)
|
|
48
|
+
print(f"Order Created: {order['order_id']}, PayDeck ID: {order['id']}")
|
|
49
|
+
|
|
50
|
+
# 2. Verify payment HMAC signature after gateway checkout
|
|
51
|
+
verified = client.verify(
|
|
52
|
+
razorpay_order_id=order['order_id'],
|
|
53
|
+
razorpay_payment_id="pay_999888777",
|
|
54
|
+
razorpay_signature="signature_hash..."
|
|
55
|
+
)
|
|
56
|
+
print(f"Payment Status: {verified['payment']['status']}")
|
|
57
|
+
|
|
58
|
+
# 3. Create a Customer & Subscription
|
|
59
|
+
customer = client.create_customer(email="dev@example.com", name="Jane Developer", external_user_id="usr_42")
|
|
60
|
+
subscription = client.create_subscription(customer_id=customer["id"], plan_id="plan_pro_month")
|
|
61
|
+
|
|
62
|
+
# 4. Verify Downstream Webhook Signatures (Static Method)
|
|
63
|
+
is_valid = PayDeckClient.verify_webhook_signature(
|
|
64
|
+
payload=raw_body_bytes_or_str,
|
|
65
|
+
signature=request_headers.get("X-PayDeck-Signature"),
|
|
66
|
+
secret="your_product_webhook_secret"
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Admin Management
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
admin_client = PayDeckClient(
|
|
74
|
+
base_url="http://localhost:8787",
|
|
75
|
+
admin_token="admin_secret_token",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Create Product
|
|
79
|
+
product = admin_client.create_product(slug="formrelay", name="FormRelay SaaS")
|
|
80
|
+
|
|
81
|
+
# Mint Product API Key
|
|
82
|
+
key = admin_client.mint_key(slug="formrelay", name="Production Worker Key", environment="live")
|
|
83
|
+
print(f"New Key: {key['key']}")
|
|
84
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
paydeck/__init__.py,sha256=k2c6XZW1sJJr5JoYgiJOgqMYYSXedODe2CW2k5H14ZM,188
|
|
2
|
+
paydeck/client.py,sha256=cMwc11-oqL9cqbmXYP1Z2so6ye4j-WX0U4g9ESMFiyk,11951
|
|
3
|
+
paydeck/exceptions.py,sha256=OfHDNw1kGruCYcY4GMwCtqWbwya5YavKXoHE3jTTUHM,729
|
|
4
|
+
paydeck-1.0.0.dist-info/METADATA,sha256=5bxLNnHeLYydK5L1pUdZxDXjfE4g8PiT8izT4GWZJAs,2729
|
|
5
|
+
paydeck-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
paydeck-1.0.0.dist-info/top_level.txt,sha256=zlRMcJQYSnd4Ilg9VcwCBfcrAhX-6rKKX_KjBB7cbhM,8
|
|
7
|
+
paydeck-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
paydeck
|