plaxswap 0.1.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.
plaxswap-0.1.0/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Plaxswap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: plaxswap
3
+ Version: 0.1.0
4
+ Summary: Official Plaxswap API client: accept crypto payments (hosted checkout, payment links, signed webhooks) and quote crypto swaps.
5
+ Author-email: Plaxswap <support@plaxswap.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://plaxswap.com
8
+ Project-URL: Documentation, https://plaxswap.com/developers/payments
9
+ Project-URL: API reference, https://plaxswap.com/api/v1/openapi.json
10
+ Project-URL: Source, https://github.com/plaxswap-com/plaxswap-python
11
+ Project-URL: Issues, https://github.com/plaxswap-com/plaxswap-python/issues
12
+ Keywords: plaxswap,crypto payments,crypto payment gateway,payment gateway,bitcoin,usdt,stablecoin,webhooks,crypto swap
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # plaxswap
25
+
26
+ The official Python client for the [Plaxswap](https://plaxswap.com) API.
27
+
28
+ - **Accept crypto payments** on your site: create a payment, send the customer to the hosted checkout, and check the signed webhook. Docs: [Crypto payment gateway API](https://plaxswap.com/developers/payments).
29
+ - **Payment links**: ask for an exact amount of one coin; the payer can use any supported coin. See [crypto payment links](https://plaxswap.com/pay).
30
+ - **Swaps**: quote routes from KyberSwap, 0x, LI.FI, Relay and NEAR Intents, best return first, with every fee shown. See the [swap API guide](https://plaxswap.com/developers).
31
+
32
+ Standard library only. Python 3.8+.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install plaxswap
38
+ ```
39
+
40
+ ## Accept a payment
41
+
42
+ ```python
43
+ import os
44
+ from plaxswap import Plaxswap
45
+
46
+ px = Plaxswap(api_key=os.environ["PLAXSWAP_KEY"]) # pxs_test_... or pxs_live_...
47
+
48
+ payment = px.payments.create(
49
+ amount="100", currency="USD", receive="usdt-tron",
50
+ orderId="1042", description="2 x T-shirt", returnUrl="https://shop.example/orders/1042",
51
+ idempotency_key="order-1042", # retries return the same payment
52
+ )
53
+ print(payment["checkoutUrl"]) # send the customer here
54
+ ```
55
+
56
+ With a test key nothing real moves. Act out an outcome and get the same webhook a live payment sends:
57
+
58
+ ```python
59
+ px.payments.simulate(payment["id"], "paid")
60
+ ```
61
+
62
+ ## Check the webhook (Flask)
63
+
64
+ ```python
65
+ from flask import Flask, request
66
+ from plaxswap import construct_event, WebhookSignatureError
67
+
68
+ app = Flask(__name__)
69
+
70
+ @app.post("/plaxswap-webhook")
71
+ def webhook():
72
+ try:
73
+ event = construct_event(request.get_data(), request.headers.get("x-px-signature"), os.environ["PLAXSWAP_WEBHOOK_SECRET"])
74
+ except WebhookSignatureError:
75
+ return "", 400
76
+ # Skip events you have seen (event["id"]), then read the payment back before you ship:
77
+ payment = px.payments.retrieve(event["data"]["id"])
78
+ if payment["status"] == "paid":
79
+ pass # fulfil order payment["orderId"]
80
+ return "", 200
81
+ ```
82
+
83
+ ## Quote a swap
84
+
85
+ ```python
86
+ px = Plaxswap() # no key needed for quotes
87
+ routes = px.swap.routes("usdt-tron", "btc", "250")["routes"]
88
+ ```
89
+
90
+ ## Errors
91
+
92
+ Every failed call raises `PlaxswapError` with `status`, a stable `code` (for example `invalid_api_key`) and a plain-English `message` that is safe to show.
93
+
94
+ ## Fees
95
+
96
+ Gateway: the customer pays the 1.5% platform fee at checkout; you pay 0.3% plus the network fee when you withdraw. Swaps: Plaxswap's fee is at most 0.3%, plus the route's own fee. Current figures: [plaxswap.com/fees](https://plaxswap.com/fees).
97
+
98
+ ## Links
99
+
100
+ - [Plaxswap](https://plaxswap.com)
101
+ - [Payment gateway docs](https://plaxswap.com/developers/payments)
102
+ - [OpenAPI document](https://plaxswap.com/api/v1/openapi.json)
103
+ - [WooCommerce plugin](https://plaxswap.com/integrations/woocommerce)
104
+ - Support: support@plaxswap.com
105
+
106
+ MIT licence.
@@ -0,0 +1,83 @@
1
+ # plaxswap
2
+
3
+ The official Python client for the [Plaxswap](https://plaxswap.com) API.
4
+
5
+ - **Accept crypto payments** on your site: create a payment, send the customer to the hosted checkout, and check the signed webhook. Docs: [Crypto payment gateway API](https://plaxswap.com/developers/payments).
6
+ - **Payment links**: ask for an exact amount of one coin; the payer can use any supported coin. See [crypto payment links](https://plaxswap.com/pay).
7
+ - **Swaps**: quote routes from KyberSwap, 0x, LI.FI, Relay and NEAR Intents, best return first, with every fee shown. See the [swap API guide](https://plaxswap.com/developers).
8
+
9
+ Standard library only. Python 3.8+.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install plaxswap
15
+ ```
16
+
17
+ ## Accept a payment
18
+
19
+ ```python
20
+ import os
21
+ from plaxswap import Plaxswap
22
+
23
+ px = Plaxswap(api_key=os.environ["PLAXSWAP_KEY"]) # pxs_test_... or pxs_live_...
24
+
25
+ payment = px.payments.create(
26
+ amount="100", currency="USD", receive="usdt-tron",
27
+ orderId="1042", description="2 x T-shirt", returnUrl="https://shop.example/orders/1042",
28
+ idempotency_key="order-1042", # retries return the same payment
29
+ )
30
+ print(payment["checkoutUrl"]) # send the customer here
31
+ ```
32
+
33
+ With a test key nothing real moves. Act out an outcome and get the same webhook a live payment sends:
34
+
35
+ ```python
36
+ px.payments.simulate(payment["id"], "paid")
37
+ ```
38
+
39
+ ## Check the webhook (Flask)
40
+
41
+ ```python
42
+ from flask import Flask, request
43
+ from plaxswap import construct_event, WebhookSignatureError
44
+
45
+ app = Flask(__name__)
46
+
47
+ @app.post("/plaxswap-webhook")
48
+ def webhook():
49
+ try:
50
+ event = construct_event(request.get_data(), request.headers.get("x-px-signature"), os.environ["PLAXSWAP_WEBHOOK_SECRET"])
51
+ except WebhookSignatureError:
52
+ return "", 400
53
+ # Skip events you have seen (event["id"]), then read the payment back before you ship:
54
+ payment = px.payments.retrieve(event["data"]["id"])
55
+ if payment["status"] == "paid":
56
+ pass # fulfil order payment["orderId"]
57
+ return "", 200
58
+ ```
59
+
60
+ ## Quote a swap
61
+
62
+ ```python
63
+ px = Plaxswap() # no key needed for quotes
64
+ routes = px.swap.routes("usdt-tron", "btc", "250")["routes"]
65
+ ```
66
+
67
+ ## Errors
68
+
69
+ Every failed call raises `PlaxswapError` with `status`, a stable `code` (for example `invalid_api_key`) and a plain-English `message` that is safe to show.
70
+
71
+ ## Fees
72
+
73
+ Gateway: the customer pays the 1.5% platform fee at checkout; you pay 0.3% plus the network fee when you withdraw. Swaps: Plaxswap's fee is at most 0.3%, plus the route's own fee. Current figures: [plaxswap.com/fees](https://plaxswap.com/fees).
74
+
75
+ ## Links
76
+
77
+ - [Plaxswap](https://plaxswap.com)
78
+ - [Payment gateway docs](https://plaxswap.com/developers/payments)
79
+ - [OpenAPI document](https://plaxswap.com/api/v1/openapi.json)
80
+ - [WooCommerce plugin](https://plaxswap.com/integrations/woocommerce)
81
+ - Support: support@plaxswap.com
82
+
83
+ MIT licence.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "plaxswap"
7
+ version = "0.1.0"
8
+ description = "Official Plaxswap API client: accept crypto payments (hosted checkout, payment links, signed webhooks) and quote crypto swaps."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Plaxswap", email = "support@plaxswap.com" }]
13
+ keywords = ["plaxswap", "crypto payments", "crypto payment gateway", "payment gateway", "bitcoin", "usdt", "stablecoin", "webhooks", "crypto swap"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Office/Business :: Financial",
20
+ "Topic :: Software Development :: Libraries",
21
+ ]
22
+ dependencies = []
23
+
24
+ [project.urls]
25
+ Homepage = "https://plaxswap.com"
26
+ Documentation = "https://plaxswap.com/developers/payments"
27
+ "API reference" = "https://plaxswap.com/api/v1/openapi.json"
28
+ Source = "https://github.com/plaxswap-com/plaxswap-python"
29
+ Issues = "https://github.com/plaxswap-com/plaxswap-python/issues"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,215 @@
1
+ """Official Python client for the Plaxswap API.
2
+
3
+ Accept crypto payments (hosted checkout, payment links, signed webhooks) and quote crypto swaps.
4
+ Docs: https://plaxswap.com/developers/payments and https://plaxswap.com/developers
5
+ Standard library only (urllib, hmac, json). Python 3.8+.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import hmac
11
+ import json
12
+ import time
13
+ import urllib.error
14
+ import urllib.parse
15
+ import urllib.request
16
+ from typing import Any, Dict, Iterable, Optional, Union
17
+
18
+ __all__ = ["Plaxswap", "PlaxswapError", "WebhookSignatureError", "verify_signature", "construct_event", "sign", "SIGNATURE_HEADER"]
19
+ __version__ = "0.1.0"
20
+
21
+ DEFAULT_BASE_URL = "https://plaxswap.com"
22
+ SIGNATURE_HEADER = "x-px-signature"
23
+
24
+
25
+ class PlaxswapError(Exception):
26
+ """An API error: HTTP status, the stable code and the plain-English message from the API."""
27
+
28
+ def __init__(self, status: int, code: str, message: str, details: Any = None):
29
+ super().__init__(message)
30
+ self.status = status
31
+ self.code = code
32
+ self.message = message
33
+ self.details = details
34
+
35
+ def __repr__(self) -> str: # pragma: no cover
36
+ return f"PlaxswapError(status={self.status}, code={self.code!r}, message={self.message!r})"
37
+
38
+
39
+ class Plaxswap:
40
+ """Client for https://plaxswap.com/api/v1.
41
+
42
+ api_key: a merchant key (pxs_test_... or pxs_live_...) for the payment gateway, or a partner key (pxk_...) for swaps.
43
+ Keep it on your server.
44
+ """
45
+
46
+ def __init__(self, api_key: Optional[str] = None, base_url: str = DEFAULT_BASE_URL, user_ip: Optional[str] = None, timeout: float = 30.0):
47
+ self.api_key = api_key
48
+ self.base_url = base_url.rstrip("/")
49
+ self.user_ip = user_ip
50
+ self.timeout = timeout
51
+ self.payments = _Payments(self)
52
+ self.payment_links = _PaymentLinks(self)
53
+ self.gateway = _Gateway(self)
54
+ self.swap = _Swap(self)
55
+
56
+ def meta(self) -> Dict[str, Any]:
57
+ """Service mode, the caller's country rules and time limits."""
58
+ return self.request("GET", "/api/v1/meta")
59
+
60
+ def coins(self) -> Dict[str, Any]:
61
+ """Coins, networks and methods."""
62
+ return self.request("GET", "/api/v1/coins")
63
+
64
+ def request(self, method: str, path: str, body: Any = None, query: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None) -> Any:
65
+ """Low-level call for any endpoint in https://plaxswap.com/api/v1/openapi.json"""
66
+ url = self.base_url + path
67
+ q = {k: v for k, v in (query or {}).items() if v is not None}
68
+ if q:
69
+ url += "?" + urllib.parse.urlencode(q)
70
+ h = {"Accept": "application/json", "User-Agent": f"plaxswap-python/{__version__}"}
71
+ if self.api_key:
72
+ h["Authorization"] = f"Bearer {self.api_key}"
73
+ if self.user_ip:
74
+ h["x-user-ip"] = self.user_ip
75
+ h.update(headers or {})
76
+ data = None
77
+ if body is not None:
78
+ data = json.dumps(body).encode("utf-8")
79
+ h["Content-Type"] = "application/json"
80
+ req = urllib.request.Request(url, data=data, method=method, headers=h)
81
+ try:
82
+ with urllib.request.urlopen(req, timeout=self.timeout) as res:
83
+ text = res.read().decode("utf-8")
84
+ return json.loads(text) if text else None
85
+ except urllib.error.HTTPError as e:
86
+ text = e.read().decode("utf-8", "replace")
87
+ try:
88
+ err = (json.loads(text) or {}).get("error") or {}
89
+ except ValueError:
90
+ err = {}
91
+ raise PlaxswapError(e.code, err.get("code") or f"http_{e.code}", err.get("message") or text[:200] or str(e), err.get("details")) from None
92
+ except urllib.error.URLError as e:
93
+ raise PlaxswapError(0, "network_error", str(e.reason)) from None
94
+
95
+
96
+ class _Payments:
97
+ def __init__(self, c: Plaxswap):
98
+ self._c = c
99
+
100
+ def create(self, idempotency_key: Optional[str] = None, **params: Any) -> Dict[str, Any]:
101
+ """Ask for a payment; send the payer to payment["checkoutUrl"].
102
+
103
+ Example: create(amount="100", currency="USD", receive="usdt-tron", orderId="1042", idempotency_key="order-1042")
104
+ """
105
+ headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
106
+ return self._c.request("POST", "/api/v1/payments", body=params, headers=headers)
107
+
108
+ def retrieve(self, payment_id: str) -> Dict[str, Any]:
109
+ return self._c.request("GET", f"/api/v1/payments/{urllib.parse.quote(payment_id, safe='')}")
110
+
111
+ def list(self, **query: Any) -> Dict[str, Any]:
112
+ """Newest first. Filters: status, orderId, createdFrom, createdTo, limit, cursor."""
113
+ return self._c.request("GET", "/api/v1/payments", query=query)
114
+
115
+ def cancel(self, payment_id: str) -> Dict[str, Any]:
116
+ return self._c.request("POST", f"/api/v1/payments/{urllib.parse.quote(payment_id, safe='')}/cancel", body={})
117
+
118
+ def simulate(self, payment_id: str, outcome: str, pay_currency: Optional[str] = None) -> Dict[str, Any]:
119
+ """Test keys only: act out paid, partially_paid, overpaid, late, expired or failed."""
120
+ body: Dict[str, Any] = {"outcome": outcome}
121
+ if pay_currency:
122
+ body["payCurrency"] = pay_currency
123
+ return self._c.request("POST", f"/api/v1/payments/{urllib.parse.quote(payment_id, safe='')}/simulate", body=body)
124
+
125
+
126
+ class _PaymentLinks:
127
+ def __init__(self, c: Plaxswap):
128
+ self._c = c
129
+
130
+ def create(self, **params: Any) -> Dict[str, Any]:
131
+ """Create a "pay me exactly this amount" link. Keep manageToken: it is shown once."""
132
+ return self._c.request("POST", "/api/v1/payment-links", body=params)
133
+
134
+ def retrieve(self, link_id: str) -> Dict[str, Any]:
135
+ return self._c.request("GET", f"/api/v1/payment-links/{urllib.parse.quote(link_id, safe='')}")
136
+
137
+ def coins(self) -> Dict[str, Any]:
138
+ return self._c.request("GET", "/api/v1/payment-links/coins")
139
+
140
+
141
+ class _Gateway:
142
+ def __init__(self, c: Plaxswap):
143
+ self._c = c
144
+
145
+ def coins(self) -> Dict[str, Any]:
146
+ """Coins you can receive, and what payers can pay each with. No key needed."""
147
+ return self._c.request("GET", "/api/v1/gateway/coins")
148
+
149
+
150
+ class _Swap:
151
+ def __init__(self, c: Plaxswap):
152
+ self._c = c
153
+
154
+ def coins(self) -> Dict[str, Any]:
155
+ return self._c.request("GET", "/api/v1/dex/coins")
156
+
157
+ def routes(self, from_: str, to: str, amount: str, **extra: Any) -> Dict[str, Any]:
158
+ """Routes for a pair and amount, best first. amount is a decimal string in coin units, for example "0.015"."""
159
+ body = {"from": from_, "to": to, "amount": amount, **extra}
160
+ return self._c.request("POST", "/api/v1/dex/routes", body=body)
161
+
162
+ def build(self, route_id: str) -> Dict[str, Any]:
163
+ return self._c.request("POST", "/api/v1/dex/build", body={"routeId": route_id})
164
+
165
+ def status(self, deposit_address: str) -> Dict[str, Any]:
166
+ return self._c.request("GET", "/api/v1/dex/status", query={"depositAddress": deposit_address})
167
+
168
+
169
+ # ---------------------------------------------------------------- webhooks
170
+
171
+ class WebhookSignatureError(Exception):
172
+ pass
173
+
174
+
175
+ def _parse(header: Optional[str]):
176
+ t, v1 = None, []
177
+ for part in str(header or "").split(","):
178
+ if "=" not in part:
179
+ continue
180
+ k, v = part.split("=", 1)
181
+ k, v = k.strip(), v.strip()
182
+ if k == "t" and len(v) == 10 and v.isdigit():
183
+ t = v
184
+ if k == "v1" and len(v) == 64 and all(ch in "0123456789abcdef" for ch in v):
185
+ v1.append(v)
186
+ return t, v1
187
+
188
+
189
+ def _b(raw: Union[str, bytes]) -> bytes:
190
+ return raw.encode("utf-8") if isinstance(raw, str) else raw
191
+
192
+
193
+ def sign(raw_body: Union[str, bytes], secret: str, timestamp: Optional[int] = None) -> str:
194
+ """The header Plaxswap would send for this body (useful in your own tests)."""
195
+ t = int(timestamp if timestamp is not None else time.time())
196
+ mac = hmac.new(secret.encode("utf-8"), f"{t}.".encode("utf-8") + _b(raw_body), hashlib.sha256).hexdigest()
197
+ return f"t={t},v1={mac}"
198
+
199
+
200
+ def verify_signature(raw_body: Union[str, bytes], header: Optional[str], secret: str, tolerance: int = 300, now: Optional[float] = None) -> bool:
201
+ """True when x-px-signature is valid for this exact raw body and was signed within `tolerance` seconds."""
202
+ t, v1 = _parse(header)
203
+ if not t or not v1 or not secret:
204
+ return False
205
+ if abs((now if now is not None else time.time()) - int(t)) > tolerance:
206
+ return False
207
+ expected = hmac.new(secret.encode("utf-8"), f"{t}.".encode("utf-8") + _b(raw_body), hashlib.sha256).hexdigest()
208
+ return any(hmac.compare_digest(sig, expected) for sig in v1)
209
+
210
+
211
+ def construct_event(raw_body: Union[str, bytes], header: Optional[str], secret: str, tolerance: int = 300) -> Dict[str, Any]:
212
+ """Checks the signature, then parses the event ({"id", "event", "livemode", "createdAt", "data"})."""
213
+ if not verify_signature(raw_body, header, secret, tolerance):
214
+ raise WebhookSignatureError("Invalid or expired Plaxswap webhook signature")
215
+ return json.loads(_b(raw_body).decode("utf-8"))
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: plaxswap
3
+ Version: 0.1.0
4
+ Summary: Official Plaxswap API client: accept crypto payments (hosted checkout, payment links, signed webhooks) and quote crypto swaps.
5
+ Author-email: Plaxswap <support@plaxswap.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://plaxswap.com
8
+ Project-URL: Documentation, https://plaxswap.com/developers/payments
9
+ Project-URL: API reference, https://plaxswap.com/api/v1/openapi.json
10
+ Project-URL: Source, https://github.com/plaxswap-com/plaxswap-python
11
+ Project-URL: Issues, https://github.com/plaxswap-com/plaxswap-python/issues
12
+ Keywords: plaxswap,crypto payments,crypto payment gateway,payment gateway,bitcoin,usdt,stablecoin,webhooks,crypto swap
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # plaxswap
25
+
26
+ The official Python client for the [Plaxswap](https://plaxswap.com) API.
27
+
28
+ - **Accept crypto payments** on your site: create a payment, send the customer to the hosted checkout, and check the signed webhook. Docs: [Crypto payment gateway API](https://plaxswap.com/developers/payments).
29
+ - **Payment links**: ask for an exact amount of one coin; the payer can use any supported coin. See [crypto payment links](https://plaxswap.com/pay).
30
+ - **Swaps**: quote routes from KyberSwap, 0x, LI.FI, Relay and NEAR Intents, best return first, with every fee shown. See the [swap API guide](https://plaxswap.com/developers).
31
+
32
+ Standard library only. Python 3.8+.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install plaxswap
38
+ ```
39
+
40
+ ## Accept a payment
41
+
42
+ ```python
43
+ import os
44
+ from plaxswap import Plaxswap
45
+
46
+ px = Plaxswap(api_key=os.environ["PLAXSWAP_KEY"]) # pxs_test_... or pxs_live_...
47
+
48
+ payment = px.payments.create(
49
+ amount="100", currency="USD", receive="usdt-tron",
50
+ orderId="1042", description="2 x T-shirt", returnUrl="https://shop.example/orders/1042",
51
+ idempotency_key="order-1042", # retries return the same payment
52
+ )
53
+ print(payment["checkoutUrl"]) # send the customer here
54
+ ```
55
+
56
+ With a test key nothing real moves. Act out an outcome and get the same webhook a live payment sends:
57
+
58
+ ```python
59
+ px.payments.simulate(payment["id"], "paid")
60
+ ```
61
+
62
+ ## Check the webhook (Flask)
63
+
64
+ ```python
65
+ from flask import Flask, request
66
+ from plaxswap import construct_event, WebhookSignatureError
67
+
68
+ app = Flask(__name__)
69
+
70
+ @app.post("/plaxswap-webhook")
71
+ def webhook():
72
+ try:
73
+ event = construct_event(request.get_data(), request.headers.get("x-px-signature"), os.environ["PLAXSWAP_WEBHOOK_SECRET"])
74
+ except WebhookSignatureError:
75
+ return "", 400
76
+ # Skip events you have seen (event["id"]), then read the payment back before you ship:
77
+ payment = px.payments.retrieve(event["data"]["id"])
78
+ if payment["status"] == "paid":
79
+ pass # fulfil order payment["orderId"]
80
+ return "", 200
81
+ ```
82
+
83
+ ## Quote a swap
84
+
85
+ ```python
86
+ px = Plaxswap() # no key needed for quotes
87
+ routes = px.swap.routes("usdt-tron", "btc", "250")["routes"]
88
+ ```
89
+
90
+ ## Errors
91
+
92
+ Every failed call raises `PlaxswapError` with `status`, a stable `code` (for example `invalid_api_key`) and a plain-English `message` that is safe to show.
93
+
94
+ ## Fees
95
+
96
+ Gateway: the customer pays the 1.5% platform fee at checkout; you pay 0.3% plus the network fee when you withdraw. Swaps: Plaxswap's fee is at most 0.3%, plus the route's own fee. Current figures: [plaxswap.com/fees](https://plaxswap.com/fees).
97
+
98
+ ## Links
99
+
100
+ - [Plaxswap](https://plaxswap.com)
101
+ - [Payment gateway docs](https://plaxswap.com/developers/payments)
102
+ - [OpenAPI document](https://plaxswap.com/api/v1/openapi.json)
103
+ - [WooCommerce plugin](https://plaxswap.com/integrations/woocommerce)
104
+ - Support: support@plaxswap.com
105
+
106
+ MIT licence.
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/plaxswap/__init__.py
5
+ src/plaxswap.egg-info/PKG-INFO
6
+ src/plaxswap.egg-info/SOURCES.txt
7
+ src/plaxswap.egg-info/dependency_links.txt
8
+ src/plaxswap.egg-info/top_level.txt
9
+ tests/test_plaxswap.py
@@ -0,0 +1 @@
1
+ plaxswap
@@ -0,0 +1,60 @@
1
+ import hashlib, hmac, json, unittest
2
+ from unittest import mock
3
+ import plaxswap
4
+ from plaxswap import Plaxswap, PlaxswapError, verify_signature, construct_event, sign
5
+
6
+ SECRET = "whsec_test_123"
7
+ BODY = '{"id":"evt_3k9q2m7x8c1v5b4n","event":"payment.paid","livemode":false,"data":{"id":"pay_7m2k9q4x8c1v5b3n","object":"payment","status":"paid"}}'
8
+ T = 1794000000
9
+ HEX = hmac.new(SECRET.encode(), f"{T}.{BODY}".encode(), hashlib.sha256).hexdigest()
10
+
11
+
12
+ class Webhooks(unittest.TestCase):
13
+ def test_valid(self): self.assertTrue(verify_signature(BODY, f"t={T},v1={HEX}", SECRET, now=T + 10))
14
+ def test_bytes_body(self): self.assertTrue(verify_signature(BODY.encode(), f"t={T},v1={HEX}", SECRET, now=T))
15
+ def test_several_v1(self): self.assertTrue(verify_signature(BODY, f"t={T},v1={'0'*64},v1={HEX}", SECRET, now=T))
16
+ def test_changed_body(self): self.assertFalse(verify_signature(BODY + " ", f"t={T},v1={HEX}", SECRET, now=T))
17
+ def test_old(self): self.assertFalse(verify_signature(BODY, f"t={T},v1={HEX}", SECRET, now=T + 301))
18
+ def test_wrong_secret(self): self.assertFalse(verify_signature(BODY, f"t={T},v1={HEX}", "whsec_other", now=T))
19
+ def test_missing(self): self.assertFalse(verify_signature(BODY, None, SECRET))
20
+ def test_sign(self): self.assertEqual(sign(BODY, SECRET, T), f"t={T},v1={HEX}")
21
+ def test_construct(self):
22
+ ev = construct_event(BODY, sign(BODY, SECRET), SECRET)
23
+ self.assertEqual(ev["event"], "payment.paid"); self.assertEqual(ev["data"]["id"], "pay_7m2k9q4x8c1v5b3n")
24
+ def test_construct_bad(self):
25
+ with self.assertRaises(plaxswap.WebhookSignatureError): construct_event(BODY, "t=1,v1=x", SECRET)
26
+
27
+
28
+ class FakeResp:
29
+ def __init__(self, body): self.body = body
30
+ def read(self): return json.dumps(self.body).encode()
31
+ def __enter__(self): return self
32
+ def __exit__(self, *a): return False
33
+
34
+
35
+ class Client(unittest.TestCase):
36
+ def test_create_payment(self):
37
+ seen = {}
38
+ def fake(req, timeout=None):
39
+ seen["url"], seen["method"], seen["headers"], seen["body"] = req.full_url, req.get_method(), dict(req.header_items()), json.loads(req.data)
40
+ return FakeResp({"id": "pay_0000000000000001", "object": "payment", "status": "pending"})
41
+ with mock.patch("urllib.request.urlopen", fake):
42
+ p = Plaxswap(api_key="pxs_test_x").payments.create(amount="100", currency="USD", receive="usdt-tron", orderId="1042", idempotency_key="order-1042")
43
+ self.assertEqual(p["id"], "pay_0000000000000001")
44
+ self.assertEqual(seen["url"], "https://plaxswap.com/api/v1/payments")
45
+ self.assertEqual(seen["method"], "POST")
46
+ self.assertEqual(seen["headers"]["Authorization"], "Bearer pxs_test_x")
47
+ self.assertEqual(seen["headers"]["Idempotency-key"], "order-1042")
48
+ self.assertEqual(seen["body"], {"amount": "100", "currency": "USD", "receive": "usdt-tron", "orderId": "1042"})
49
+
50
+ def test_error(self):
51
+ import urllib.error, io
52
+ def fake(req, timeout=None):
53
+ raise urllib.error.HTTPError(req.full_url, 401, "Unauthorized", {}, io.BytesIO(b'{"error":{"code":"invalid_api_key","message":"That key is wrong."}}'))
54
+ with mock.patch("urllib.request.urlopen", fake):
55
+ with self.assertRaises(PlaxswapError) as cm: Plaxswap(api_key="bad").payments.retrieve("pay_x")
56
+ self.assertEqual((cm.exception.status, cm.exception.code), (401, "invalid_api_key"))
57
+
58
+
59
+ if __name__ == "__main__":
60
+ unittest.main()