kkhay 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.
kkhay-1.0.0/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+ .env
22
+ .venv
23
+ env/
24
+ venv/
25
+ ENV/
26
+ .pytest_cache/
27
+
kkhay-1.0.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 K Khay (https://kkhay.com)
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.
22
+
kkhay-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.5
2
+ Name: kkhay
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for K Khay Sovereign Crypto Payment Gateway
5
+ Project-URL: Homepage, https://kkhay.com
6
+ Project-URL: Documentation, https://docs.kkhay.com
7
+ Project-URL: Repository, https://github.com/boboaungdev/kkhay-python
8
+ Project-URL: Issues, https://github.com/boboaungdev/kkhay-python/issues
9
+ Author-email: K Khay <dev@kkhay.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: arbitrum,base,blockchain,bnb,bsc,checkout,crypto,cryptocurrency,eth,invoice,ipn,kkhay,payment-gateway,payment-processor,payments,polygon,usdc,usdt,web3,webhook
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+
29
+ # K Khay Python SDK 🐍
30
+
31
+ Official Python SDK for the **[K Khay Sovereign Crypto Payment Gateway](https://kkhay.com)**.
32
+
33
+ Accept non-custodial and custodial crypto payments (USDT, USDC, BNB, ETH, and native tokens across BSC, Polygon, Arbitrum, Base, and Ethereum) directly in your Python applications, Django, FastAPI, Flask, or background bots with **zero third-party dependencies**.
34
+
35
+ ---
36
+
37
+ ## 📦 Installation
38
+
39
+ ```bash
40
+ pip install kkhay
41
+ ```
42
+
43
+ *(Requires Python 3.8 or newer)*
44
+
45
+ ---
46
+
47
+ ## ⚡ Quick Start
48
+
49
+ ### 1. Initialize Client
50
+
51
+ ```python
52
+ from kkhay import KkhayClient
53
+
54
+ client = KkhayClient(api_key="kkhay_live_your_api_key_here")
55
+ ```
56
+
57
+ ### 2. Create a Payment Invoice
58
+
59
+ ```python
60
+ response = client.create_invoice(
61
+ price_amount=49.99,
62
+ price_currency="USD",
63
+ pay_network="bsc",
64
+ pay_token="USDT",
65
+ order_id="ORDER-9012",
66
+ title="Annual Premium Membership",
67
+ customer_email="merchant-customer@example.com",
68
+ redirect_url="https://myshop.com/orders/success",
69
+ cancel_url="https://myshop.com/cart",
70
+ )
71
+
72
+ invoice = response["invoice"]
73
+ print("Invoice ID:", invoice.id)
74
+ print("Deposit Address:", invoice.deposit_address)
75
+ print("Customer Checkout URL:", invoice.hosted_url)
76
+ ```
77
+
78
+ ### 3. Retrieve Invoice Status & Blockchain Proofs
79
+
80
+ ```python
81
+ data = client.get_invoice("inv_9f81a7b2")
82
+ invoice = data.invoice
83
+
84
+ print(f"Status: {invoice.status}")
85
+ for payment in data.payments:
86
+ print(f"Received: {payment.amount_received} {invoice.pay_token}")
87
+ print(f"Tx Hash: {payment.tx_hash} ({payment.confirmations} confirmations)")
88
+ ```
89
+
90
+ ### 4. List & Paginate Invoices
91
+
92
+ ```python
93
+ res = client.list_invoices(page=1, limit=10, status="FINISHED")
94
+ print(f"Total Completed Invoices: {res.total_count}")
95
+ for inv in res.items:
96
+ print(f"- {inv.id}: ${inv.price_amount} ({inv.status})")
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 🔐 Webhook / IPN Verification
102
+
103
+ K Khay cryptographically signs every webhook notification using HMAC-SHA256 with your secret IPN key.
104
+
105
+ ### FastAPI Example
106
+
107
+ ```python
108
+ from fastapi import FastAPI, Header, HTTPException, Request
109
+ from kkhay import SignatureVerificationError, parse_webhook_event
110
+
111
+ app = FastAPI()
112
+ IPN_SECRET = "whsec_your_secret_key"
113
+
114
+
115
+ @app.post("/api/webhooks/kkhay")
116
+ async def handle_kkhay_webhook(
117
+ request: Request,
118
+ x_kkhay_signature: str = Header(None),
119
+ ):
120
+ body = await request.body()
121
+
122
+ try:
123
+ event = parse_webhook_event(
124
+ raw_body=body,
125
+ signature=x_kkhay_signature,
126
+ ipn_secret=IPN_SECRET,
127
+ )
128
+ except SignatureVerificationError:
129
+ raise HTTPException(status_code=400, detail="Invalid signature")
130
+
131
+ if event.event == "payment.finished":
132
+ print(f"Order {event.order_id} was paid!")
133
+ print(f"Transaction Hash: {event.tx_hash}")
134
+
135
+ return {"status": "ok"}
136
+ ```
137
+
138
+ ### Flask / Django Example
139
+
140
+ ```python
141
+ from flask import Flask, abort, request
142
+ from kkhay import SignatureVerificationError, parse_webhook_event
143
+
144
+ app = Flask(__name__)
145
+
146
+
147
+ @app.route("/webhooks/kkhay", methods=["POST"])
148
+ def webhook():
149
+ raw_body = request.get_data()
150
+ sig = request.headers.get("x-kkhay-signature")
151
+
152
+ try:
153
+ event = parse_webhook_event(raw_body, sig, "whsec_your_secret")
154
+ except SignatureVerificationError:
155
+ abort(400)
156
+
157
+ # Process order
158
+ return "OK", 200
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🛠️ Error Handling
164
+
165
+ ```python
166
+ from kkhay import KkhayApiError, KkhayClient
167
+
168
+ client = KkhayClient(api_key="invalid_key")
169
+
170
+ try:
171
+ client.get_invoice("inv_not_exist")
172
+ except KkhayApiError as e:
173
+ print(f"HTTP Status: {e.status}")
174
+ print(f"Error Code: {e.code}")
175
+ print(f"Message: {e.message}")
176
+ ```
177
+
178
+ ---
179
+
180
+ ## 📄 License
181
+
182
+ MIT © [K Khay](https://kkhay.com)
183
+
kkhay-1.0.0/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # K Khay Python SDK 🐍
2
+
3
+ Official Python SDK for the **[K Khay Sovereign Crypto Payment Gateway](https://kkhay.com)**.
4
+
5
+ Accept non-custodial and custodial crypto payments (USDT, USDC, BNB, ETH, and native tokens across BSC, Polygon, Arbitrum, Base, and Ethereum) directly in your Python applications, Django, FastAPI, Flask, or background bots with **zero third-party dependencies**.
6
+
7
+ ---
8
+
9
+ ## 📦 Installation
10
+
11
+ ```bash
12
+ pip install kkhay
13
+ ```
14
+
15
+ *(Requires Python 3.8 or newer)*
16
+
17
+ ---
18
+
19
+ ## ⚡ Quick Start
20
+
21
+ ### 1. Initialize Client
22
+
23
+ ```python
24
+ from kkhay import KkhayClient
25
+
26
+ client = KkhayClient(api_key="kkhay_live_your_api_key_here")
27
+ ```
28
+
29
+ ### 2. Create a Payment Invoice
30
+
31
+ ```python
32
+ response = client.create_invoice(
33
+ price_amount=49.99,
34
+ price_currency="USD",
35
+ pay_network="bsc",
36
+ pay_token="USDT",
37
+ order_id="ORDER-9012",
38
+ title="Annual Premium Membership",
39
+ customer_email="merchant-customer@example.com",
40
+ redirect_url="https://myshop.com/orders/success",
41
+ cancel_url="https://myshop.com/cart",
42
+ )
43
+
44
+ invoice = response["invoice"]
45
+ print("Invoice ID:", invoice.id)
46
+ print("Deposit Address:", invoice.deposit_address)
47
+ print("Customer Checkout URL:", invoice.hosted_url)
48
+ ```
49
+
50
+ ### 3. Retrieve Invoice Status & Blockchain Proofs
51
+
52
+ ```python
53
+ data = client.get_invoice("inv_9f81a7b2")
54
+ invoice = data.invoice
55
+
56
+ print(f"Status: {invoice.status}")
57
+ for payment in data.payments:
58
+ print(f"Received: {payment.amount_received} {invoice.pay_token}")
59
+ print(f"Tx Hash: {payment.tx_hash} ({payment.confirmations} confirmations)")
60
+ ```
61
+
62
+ ### 4. List & Paginate Invoices
63
+
64
+ ```python
65
+ res = client.list_invoices(page=1, limit=10, status="FINISHED")
66
+ print(f"Total Completed Invoices: {res.total_count}")
67
+ for inv in res.items:
68
+ print(f"- {inv.id}: ${inv.price_amount} ({inv.status})")
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 🔐 Webhook / IPN Verification
74
+
75
+ K Khay cryptographically signs every webhook notification using HMAC-SHA256 with your secret IPN key.
76
+
77
+ ### FastAPI Example
78
+
79
+ ```python
80
+ from fastapi import FastAPI, Header, HTTPException, Request
81
+ from kkhay import SignatureVerificationError, parse_webhook_event
82
+
83
+ app = FastAPI()
84
+ IPN_SECRET = "whsec_your_secret_key"
85
+
86
+
87
+ @app.post("/api/webhooks/kkhay")
88
+ async def handle_kkhay_webhook(
89
+ request: Request,
90
+ x_kkhay_signature: str = Header(None),
91
+ ):
92
+ body = await request.body()
93
+
94
+ try:
95
+ event = parse_webhook_event(
96
+ raw_body=body,
97
+ signature=x_kkhay_signature,
98
+ ipn_secret=IPN_SECRET,
99
+ )
100
+ except SignatureVerificationError:
101
+ raise HTTPException(status_code=400, detail="Invalid signature")
102
+
103
+ if event.event == "payment.finished":
104
+ print(f"Order {event.order_id} was paid!")
105
+ print(f"Transaction Hash: {event.tx_hash}")
106
+
107
+ return {"status": "ok"}
108
+ ```
109
+
110
+ ### Flask / Django Example
111
+
112
+ ```python
113
+ from flask import Flask, abort, request
114
+ from kkhay import SignatureVerificationError, parse_webhook_event
115
+
116
+ app = Flask(__name__)
117
+
118
+
119
+ @app.route("/webhooks/kkhay", methods=["POST"])
120
+ def webhook():
121
+ raw_body = request.get_data()
122
+ sig = request.headers.get("x-kkhay-signature")
123
+
124
+ try:
125
+ event = parse_webhook_event(raw_body, sig, "whsec_your_secret")
126
+ except SignatureVerificationError:
127
+ abort(400)
128
+
129
+ # Process order
130
+ return "OK", 200
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 🛠️ Error Handling
136
+
137
+ ```python
138
+ from kkhay import KkhayApiError, KkhayClient
139
+
140
+ client = KkhayClient(api_key="invalid_key")
141
+
142
+ try:
143
+ client.get_invoice("inv_not_exist")
144
+ except KkhayApiError as e:
145
+ print(f"HTTP Status: {e.status}")
146
+ print(f"Error Code: {e.code}")
147
+ print(f"Message: {e.message}")
148
+ ```
149
+
150
+ ---
151
+
152
+ ## 📄 License
153
+
154
+ MIT © [K Khay](https://kkhay.com)
155
+
@@ -0,0 +1,35 @@
1
+ """
2
+ Official Python SDK for K Khay Sovereign Crypto Payment Gateway.
3
+ """
4
+
5
+ from .client import KkhayClient
6
+ from .errors import KkhayApiError, SignatureVerificationError
7
+ from .types import (
8
+ CreateInvoiceRequest,
9
+ GetInvoiceResponse,
10
+ InvoiceStatus,
11
+ KkhayInvoice,
12
+ KkhayPaymentRecord,
13
+ KkhayWebhookEvent,
14
+ ListInvoicesResponse,
15
+ PaymentNetwork,
16
+ )
17
+ from .webhook import parse_webhook_event, verify_webhook_signature
18
+
19
+ __version__ = "1.0.0"
20
+ __all__ = [
21
+ "KkhayClient",
22
+ "KkhayApiError",
23
+ "SignatureVerificationError",
24
+ "CreateInvoiceRequest",
25
+ "GetInvoiceResponse",
26
+ "KkhayInvoice",
27
+ "KkhayPaymentRecord",
28
+ "KkhayWebhookEvent",
29
+ "ListInvoicesResponse",
30
+ "InvoiceStatus",
31
+ "PaymentNetwork",
32
+ "verify_webhook_signature",
33
+ "parse_webhook_event",
34
+ ]
35
+
@@ -0,0 +1,212 @@
1
+ import json
2
+ import urllib.error
3
+ import urllib.parse
4
+ import urllib.request
5
+ from typing import Any, Dict, Optional, Union
6
+
7
+ from .errors import KkhayApiError
8
+ from .types import (
9
+ CreateInvoiceRequest,
10
+ GetInvoiceResponse,
11
+ KkhayInvoice,
12
+ KkhayPaymentRecord,
13
+ ListInvoicesResponse,
14
+ )
15
+
16
+
17
+ class KkhayClient:
18
+ """
19
+ Official Python SDK client for the K Khay Sovereign Crypto Payment Gateway.
20
+
21
+ Example:
22
+ >>> from kkhay import KkhayClient
23
+ >>> client = KkhayClient(api_key="kkhay_live_...")
24
+ >>> res = client.create_invoice(
25
+ ... price_amount=49.99,
26
+ ... pay_network="bsc",
27
+ ... pay_token="USDT",
28
+ ... title="Pro Subscription",
29
+ ... )
30
+ >>> print("Pay URL:", res["invoice"].hosted_url)
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ api_key: str,
36
+ base_url: str = "https://api.kkhay.com",
37
+ timeout: float = 30.0,
38
+ ):
39
+ if not api_key or not isinstance(api_key, str) or not api_key.strip():
40
+ raise ValueError("KkhayClient: A valid 'api_key' string is required.")
41
+
42
+ self.api_key: str = api_key.strip()
43
+ self.base_url: str = base_url.rstrip("/")
44
+ self.timeout: float = float(timeout)
45
+
46
+ def _get_url(self, path: str) -> str:
47
+ clean_path = path if path.startswith("/") else f"/{path}"
48
+ if self.base_url.endswith("/api") or "api." in self.base_url:
49
+ return f"{self.base_url}{clean_path}"
50
+ return f"{self.base_url}/api{clean_path}"
51
+
52
+ def _request(
53
+ self,
54
+ path: str,
55
+ method: str = "GET",
56
+ data: Optional[Dict[str, Any]] = None,
57
+ ) -> Dict[str, Any]:
58
+ url = self._get_url(path)
59
+ headers = {
60
+ "Accept": "application/json",
61
+ "x-api-key": self.api_key,
62
+ "User-Agent": "kkhay-python/1.0.0",
63
+ }
64
+
65
+ body_bytes: Optional[bytes] = None
66
+ if data is not None:
67
+ headers["Content-Type"] = "application/json"
68
+ body_bytes = json.dumps(data).encode("utf-8")
69
+
70
+ req = urllib.request.Request(url, data=body_bytes, headers=headers, method=method)
71
+
72
+ try:
73
+ with urllib.request.urlopen(req, timeout=self.timeout) as response:
74
+ status = response.status
75
+ raw = response.read().decode("utf-8")
76
+ if not raw:
77
+ return {}
78
+ try:
79
+ return json.loads(raw)
80
+ except json.JSONDecodeError:
81
+ return {"data": raw}
82
+ except urllib.error.HTTPError as e:
83
+ raw_err = e.read().decode("utf-8") if e.fp else ""
84
+ error_message = f"Request failed with HTTP {e.code}"
85
+ error_code = None
86
+ error_details = None
87
+
88
+ if raw_err:
89
+ try:
90
+ err_json = json.loads(raw_err)
91
+ error_message = err_json.get("message") or err_json.get("error") or error_message
92
+ error_code = err_json.get("code")
93
+ error_details = err_json.get("details")
94
+ except json.JSONDecodeError:
95
+ error_message = raw_err
96
+
97
+ raise KkhayApiError(
98
+ status=e.code,
99
+ message=error_message,
100
+ code=error_code,
101
+ details=error_details,
102
+ ) from e
103
+ except urllib.error.URLError as e:
104
+ if "timed out" in str(e).lower():
105
+ raise KkhayApiError(408, f"Request timed out after {self.timeout}s") from e
106
+ raise KkhayApiError(500, f"Network connection error: {e.reason}") from e
107
+ except Exception as e:
108
+ raise KkhayApiError(500, f"Unexpected error: {e}") from e
109
+
110
+ def create_invoice(
111
+ self,
112
+ price_amount: float,
113
+ pay_network: str,
114
+ pay_token: str,
115
+ price_currency: str = "USD",
116
+ order_id: Optional[str] = None,
117
+ title: Optional[str] = None,
118
+ customer_name: Optional[str] = None,
119
+ customer_email: Optional[str] = None,
120
+ redirect_url: Optional[str] = None,
121
+ cancel_url: Optional[str] = None,
122
+ ipn_callback_url: Optional[str] = None,
123
+ metadata: Optional[Dict[str, Any]] = None,
124
+ ) -> Dict[str, Any]:
125
+ """
126
+ Creates a new sovereign crypto payment invoice.
127
+
128
+ :return: Dict with keys 'ok' (bool) and 'invoice' (KkhayInvoice)
129
+ """
130
+ if price_amount <= 0:
131
+ raise ValueError("create_invoice: 'price_amount' must be a positive number.")
132
+ if not pay_network:
133
+ raise ValueError("create_invoice: 'pay_network' is required (e.g. 'bsc', 'polygon').")
134
+ if not pay_token:
135
+ raise ValueError("create_invoice: 'pay_token' is required (e.g. 'USDT', 'USDC').")
136
+
137
+ payload = CreateInvoiceRequest(
138
+ price_amount=price_amount,
139
+ pay_network=pay_network,
140
+ pay_token=pay_token,
141
+ price_currency=price_currency,
142
+ order_id=order_id,
143
+ title=title,
144
+ customer_name=customer_name,
145
+ customer_email=customer_email,
146
+ redirect_url=redirect_url,
147
+ cancel_url=cancel_url,
148
+ ipn_callback_url=ipn_callback_url,
149
+ metadata=metadata,
150
+ ).to_dict()
151
+
152
+ res = self._request("/v1/merchant/invoices", method="POST", data=payload)
153
+ return {
154
+ "ok": bool(res.get("ok", True)),
155
+ "invoice": KkhayInvoice.from_dict(res.get("invoice", {})),
156
+ }
157
+
158
+ def get_invoice(self, invoice_id: str) -> GetInvoiceResponse:
159
+ """
160
+ Retrieve details and blockchain confirmations for an invoice by ID.
161
+ """
162
+ if not invoice_id or not str(invoice_id).strip():
163
+ raise ValueError("get_invoice: 'invoice_id' is required.")
164
+
165
+ res = self._request(f"/v1/merchant/invoices/{urllib.parse.quote(str(invoice_id).strip())}")
166
+ invoice_raw = res.get("invoice", {})
167
+ payments_raw = res.get("payments", [])
168
+
169
+ return GetInvoiceResponse(
170
+ ok=bool(res.get("ok", True)),
171
+ invoice=KkhayInvoice.from_dict(invoice_raw),
172
+ payments=[KkhayPaymentRecord.from_dict(p) for p in payments_raw],
173
+ )
174
+
175
+ def list_invoices(
176
+ self,
177
+ page: int = 1,
178
+ limit: int = 20,
179
+ status: Optional[str] = None,
180
+ search: Optional[str] = None,
181
+ ) -> ListInvoicesResponse:
182
+ """
183
+ List and paginate merchant invoices.
184
+ """
185
+ params: Dict[str, str] = {
186
+ "page": str(page),
187
+ "limit": str(limit),
188
+ }
189
+ if status:
190
+ params["status"] = status
191
+ if search:
192
+ params["search"] = search
193
+
194
+ query_str = urllib.parse.urlencode(params)
195
+ res = self._request(f"/v1/merchant/invoices?{query_str}")
196
+
197
+ items_raw = res.get("items", [])
198
+ return ListInvoicesResponse(
199
+ ok=bool(res.get("ok", True)),
200
+ items=[KkhayInvoice.from_dict(item) for item in items_raw],
201
+ total_count=int(res.get("totalCount", 0)),
202
+ total_pages=int(res.get("totalPages", 1)),
203
+ current_page=int(res.get("currentPage", page)),
204
+ limit=int(res.get("limit", limit)),
205
+ )
206
+
207
+ def check_health(self) -> Dict[str, Any]:
208
+ """
209
+ Check connectivity and health of the K Khay API gateway.
210
+ """
211
+ return self._request("/health")
212
+
@@ -0,0 +1,31 @@
1
+ from typing import Any, Optional
2
+
3
+
4
+ class KkhayApiError(Exception):
5
+ """
6
+ Standard API error returned when a K Khay endpoint fails.
7
+ """
8
+
9
+ def __init__(
10
+ self,
11
+ status: int,
12
+ message: str,
13
+ code: Optional[str] = None,
14
+ details: Optional[Any] = None,
15
+ ):
16
+ super().__init__(f"KkhayApiError [HTTP {status}]: {message}")
17
+ self.status = status
18
+ self.message = message
19
+ self.code = code
20
+ self.details = details
21
+
22
+ def __repr__(self) -> str:
23
+ return f"<KkhayApiError status={self.status} code={self.code!r} message={self.message!r}>"
24
+
25
+
26
+ class SignatureVerificationError(Exception):
27
+ """
28
+ Raised when an incoming IPN webhook payload fails HMAC signature verification.
29
+ """
30
+ pass
31
+
@@ -0,0 +1,182 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Dict, List, Literal, Optional, Union
3
+
4
+ InvoiceStatus = Literal[
5
+ "WAITING",
6
+ "DETECTING",
7
+ "CONFIRMING",
8
+ "SENDING",
9
+ "FINISHED",
10
+ "EXPIRED",
11
+ "CANCELED",
12
+ "FAILED",
13
+ ]
14
+
15
+ PaymentNetwork = Literal["bsc", "polygon", "base", "arbitrum", "ethereum", str]
16
+
17
+
18
+ @dataclass
19
+ class CreateInvoiceRequest:
20
+ """Parameters required to generate a new crypto invoice."""
21
+ price_amount: float
22
+ pay_network: Union[str, PaymentNetwork]
23
+ pay_token: str
24
+ price_currency: str = "USD"
25
+ order_id: Optional[str] = None
26
+ title: Optional[str] = None
27
+ customer_name: Optional[str] = None
28
+ customer_email: Optional[str] = None
29
+ redirect_url: Optional[str] = None
30
+ cancel_url: Optional[str] = None
31
+ ipn_callback_url: Optional[str] = None
32
+ metadata: Optional[Dict[str, Any]] = None
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ data: Dict[str, Any] = {
36
+ "priceAmount": self.price_amount,
37
+ "payNetwork": self.pay_network,
38
+ "payToken": self.pay_token,
39
+ "priceCurrency": self.price_currency,
40
+ }
41
+ if self.order_id is not None:
42
+ data["orderId"] = self.order_id
43
+ if self.title is not None:
44
+ data["title"] = self.title
45
+ if self.customer_name is not None:
46
+ data["customerName"] = self.customer_name
47
+ if self.customer_email is not None:
48
+ data["customerEmail"] = self.customer_email
49
+ if self.redirect_url is not None:
50
+ data["redirectUrl"] = self.redirect_url
51
+ if self.cancel_url is not None:
52
+ data["cancelUrl"] = self.cancel_url
53
+ if self.ipn_callback_url is not None:
54
+ data["ipnCallbackUrl"] = self.ipn_callback_url
55
+ if self.metadata is not None:
56
+ data["metadata"] = self.metadata
57
+ return data
58
+
59
+
60
+ @dataclass
61
+ class KkhayPaymentRecord:
62
+ id: str
63
+ tx_hash: str
64
+ amount_received: str
65
+ confirmations: int
66
+ status: str
67
+ forwarded_tx_hash: Optional[str] = None
68
+ created_at: str = ""
69
+
70
+ @classmethod
71
+ def from_dict(cls, data: Dict[str, Any]) -> "KkhayPaymentRecord":
72
+ return cls(
73
+ id=data.get("id", ""),
74
+ tx_hash=data.get("txHash", ""),
75
+ amount_received=str(data.get("amountReceived", "0")),
76
+ confirmations=int(data.get("confirmations", 0)),
77
+ status=data.get("status", ""),
78
+ forwarded_tx_hash=data.get("forwardedTxHash"),
79
+ created_at=data.get("createdAt", ""),
80
+ )
81
+
82
+
83
+ @dataclass
84
+ class KkhayInvoice:
85
+ id: str
86
+ merchant_id: str
87
+ price_amount: float
88
+ price_currency: str
89
+ pay_amount: str
90
+ pay_token: str
91
+ pay_network: str
92
+ deposit_address: str
93
+ status: str
94
+ fee_amount: str
95
+ net_amount: str
96
+ hosted_url: str
97
+ expires_at: str
98
+ created_at: str
99
+ order_id: Optional[str] = None
100
+ title: Optional[str] = None
101
+ settlement_mode: Optional[str] = None
102
+ payment_method: Optional[str] = None
103
+ redirect_url: Optional[str] = None
104
+ cancel_url: Optional[str] = None
105
+
106
+ @classmethod
107
+ def from_dict(cls, data: Dict[str, Any]) -> "KkhayInvoice":
108
+ return cls(
109
+ id=data.get("id", ""),
110
+ merchant_id=data.get("merchantId", ""),
111
+ price_amount=float(data.get("priceAmount", 0)),
112
+ price_currency=data.get("priceCurrency", "USD"),
113
+ pay_amount=str(data.get("payAmount", "0")),
114
+ pay_token=data.get("payToken", ""),
115
+ pay_network=data.get("payNetwork", ""),
116
+ deposit_address=data.get("depositAddress", ""),
117
+ status=data.get("status", "WAITING"),
118
+ fee_amount=str(data.get("feeAmount", "0")),
119
+ net_amount=str(data.get("netAmount", "0")),
120
+ hosted_url=data.get("hostedUrl", ""),
121
+ expires_at=data.get("expiresAt", ""),
122
+ created_at=data.get("createdAt", ""),
123
+ order_id=data.get("orderId"),
124
+ title=data.get("title"),
125
+ settlement_mode=data.get("settlementMode"),
126
+ payment_method=data.get("paymentMethod"),
127
+ redirect_url=data.get("redirectUrl"),
128
+ cancel_url=data.get("cancelUrl"),
129
+ )
130
+
131
+
132
+ @dataclass
133
+ class GetInvoiceResponse:
134
+ ok: bool
135
+ invoice: KkhayInvoice
136
+ payments: List[KkhayPaymentRecord] = field(default_factory=list)
137
+
138
+
139
+ @dataclass
140
+ class ListInvoicesResponse:
141
+ ok: bool
142
+ items: List[KkhayInvoice]
143
+ total_count: int
144
+ total_pages: int
145
+ current_page: int
146
+ limit: int
147
+
148
+
149
+ @dataclass
150
+ class KkhayWebhookEvent:
151
+ event: str
152
+ invoice_id: str
153
+ order_id: Optional[str]
154
+ price_amount: float
155
+ price_currency: str
156
+ pay_amount: str
157
+ pay_token: str
158
+ pay_network: str
159
+ deposit_address: str
160
+ status: str
161
+ timestamp: str
162
+ tx_hash: Optional[str] = None
163
+ raw_data: Dict[str, Any] = field(default_factory=dict)
164
+
165
+ @classmethod
166
+ def from_dict(cls, data: Dict[str, Any]) -> "KkhayWebhookEvent":
167
+ return cls(
168
+ event=data.get("event", ""),
169
+ invoice_id=data.get("invoice_id", ""),
170
+ order_id=data.get("order_id"),
171
+ price_amount=float(data.get("price_amount", 0)),
172
+ price_currency=data.get("price_currency", "USD"),
173
+ pay_amount=str(data.get("pay_amount", "0")),
174
+ pay_token=data.get("pay_token", ""),
175
+ pay_network=data.get("pay_network", ""),
176
+ deposit_address=data.get("deposit_address", ""),
177
+ status=data.get("status", ""),
178
+ timestamp=data.get("timestamp", ""),
179
+ tx_hash=data.get("tx_hash"),
180
+ raw_data=data,
181
+ )
182
+
@@ -0,0 +1,69 @@
1
+ import hashlib
2
+ import hmac
3
+ import json
4
+ import secrets
5
+ from typing import Any, Dict, Optional, Union
6
+
7
+ from .errors import SignatureVerificationError
8
+ from .types import KkhayWebhookEvent
9
+
10
+
11
+ def verify_webhook_signature(
12
+ payload: Union[str, bytes, Dict[str, Any]],
13
+ signature: Optional[str] = None,
14
+ ipn_secret: Optional[str] = None,
15
+ ) -> bool:
16
+ """
17
+ Verifies the cryptographic HMAC-SHA256 signature sent with incoming K Khay IPN webhooks.
18
+
19
+ :param payload: The raw request body string, bytes, or parsed dict
20
+ :param signature: The signature header value ('x-kkhay-signature')
21
+ :param ipn_secret: Your merchant IPN Secret Key
22
+ :return: True if valid, False otherwise
23
+ """
24
+ if not signature or not ipn_secret:
25
+ return False
26
+
27
+ if isinstance(payload, bytes):
28
+ raw_bytes = payload
29
+ elif isinstance(payload, str):
30
+ raw_bytes = payload.encode("utf-8")
31
+ else:
32
+ raw_bytes = json.dumps(payload, separators=(",", ":")).encode("utf-8")
33
+
34
+ try:
35
+ secret_bytes = ipn_secret.encode("utf-8")
36
+ computed_hex = hmac.new(secret_bytes, raw_bytes, hashlib.sha256).hexdigest()
37
+ return secrets.compare_digest(computed_hex, signature.strip())
38
+ except Exception:
39
+ return False
40
+
41
+
42
+ def parse_webhook_event(
43
+ raw_body: Union[str, bytes],
44
+ signature: Optional[str],
45
+ ipn_secret: str,
46
+ ) -> KkhayWebhookEvent:
47
+ """
48
+ Validates and safely parses an incoming K Khay webhook event.
49
+ Raises SignatureVerificationError if signature does not match.
50
+
51
+ :param raw_body: The raw request body
52
+ :param signature: The 'x-kkhay-signature' header value
53
+ :param ipn_secret: Your merchant IPN Secret Key
54
+ :return: Typed KkhayWebhookEvent
55
+ """
56
+ if not verify_webhook_signature(raw_body, signature, ipn_secret):
57
+ raise SignatureVerificationError("Invalid K Khay webhook signature: Request rejected.")
58
+
59
+ try:
60
+ if isinstance(raw_body, bytes):
61
+ text = raw_body.decode("utf-8")
62
+ else:
63
+ text = raw_body
64
+ data = json.loads(text)
65
+ return KkhayWebhookEvent.from_dict(data)
66
+ except Exception as e:
67
+ if isinstance(e, SignatureVerificationError):
68
+ raise
69
+ raise ValueError(f"Failed to parse webhook JSON payload: {e}") from e
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kkhay"
7
+ version = "1.0.0"
8
+ description = "Official Python SDK for K Khay Sovereign Crypto Payment Gateway"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ { name = "K Khay", email = "dev@kkhay.com" },
14
+ ]
15
+ keywords = [
16
+ "kkhay",
17
+ "crypto",
18
+ "cryptocurrency",
19
+ "payments",
20
+ "payment-gateway",
21
+ "payment-processor",
22
+ "web3",
23
+ "blockchain",
24
+ "invoice",
25
+ "checkout",
26
+ "webhook",
27
+ "ipn",
28
+ "usdt",
29
+ "usdc",
30
+ "bnb",
31
+ "eth",
32
+ "bsc",
33
+ "polygon",
34
+ "arbitrum",
35
+ "base",
36
+ ]
37
+ classifiers = [
38
+ "Development Status :: 5 - Production/Stable",
39
+ "Intended Audience :: Developers",
40
+ "License :: OSI Approved :: MIT License",
41
+ "Programming Language :: Python :: 3",
42
+ "Programming Language :: Python :: 3.8",
43
+ "Programming Language :: Python :: 3.9",
44
+ "Programming Language :: Python :: 3.10",
45
+ "Programming Language :: Python :: 3.11",
46
+ "Programming Language :: Python :: 3.12",
47
+ "Programming Language :: Python :: 3.13",
48
+ "Programming Language :: Python :: 3.14",
49
+ "Topic :: Office/Business :: Financial :: Point-Of-Sale",
50
+ "Topic :: Software Development :: Libraries :: Python Modules",
51
+ ]
52
+
53
+ [project.urls]
54
+ Homepage = "https://kkhay.com"
55
+ Documentation = "https://docs.kkhay.com"
56
+ Repository = "https://github.com/boboaungdev/kkhay-python"
57
+ Issues = "https://github.com/boboaungdev/kkhay-python/issues"
58
+
59
+ [tool.hatch.build.targets.sdist]
60
+ include = [
61
+ "/kkhay",
62
+ "/README.md",
63
+ "/LICENSE",
64
+ ]
65
+
66
+ [tool.hatch.build.targets.wheel]
67
+ packages = ["kkhay"]
68
+