decane-sdk 0.8.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.
decane/__init__.py ADDED
@@ -0,0 +1,45 @@
1
+ """Decane — Official Python SDK for institutional liquidity rail for crypto settlement."""
2
+
3
+ from .client import Decane, AsyncDecane
4
+ from .errors import (
5
+ DecaneError,
6
+ AuthenticationError,
7
+ NotFoundError,
8
+ ValidationError,
9
+ RateLimitError,
10
+ ServerError,
11
+ InvalidSignatureError,
12
+ )
13
+ from .types import (
14
+ Invoice,
15
+ InvoiceStatus,
16
+ Payment,
17
+ Payout,
18
+ PayoutStatus,
19
+ Wallet,
20
+ SwapQuote,
21
+ Swap,
22
+ WebhookEvent,
23
+ )
24
+
25
+ __version__ = "0.8.0"
26
+ __all__ = [
27
+ "Decane",
28
+ "AsyncDecane",
29
+ "DecaneError",
30
+ "AuthenticationError",
31
+ "NotFoundError",
32
+ "ValidationError",
33
+ "RateLimitError",
34
+ "ServerError",
35
+ "InvalidSignatureError",
36
+ "Invoice",
37
+ "InvoiceStatus",
38
+ "Payment",
39
+ "Payout",
40
+ "PayoutStatus",
41
+ "Wallet",
42
+ "SwapQuote",
43
+ "Swap",
44
+ "WebhookEvent",
45
+ ]
File without changes
@@ -0,0 +1,195 @@
1
+ """HTTP transport layer for Decane SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ import uuid
7
+ from typing import Any
8
+
9
+ import httpx
10
+
11
+ from ..errors import (
12
+ AuthenticationError,
13
+ DecaneError,
14
+ NotFoundError,
15
+ RateLimitError,
16
+ ServerError,
17
+ ValidationError,
18
+ )
19
+
20
+ DEFAULT_BASE_URL = "https://api.decane.io/api/v1/"
21
+ DEFAULT_TIMEOUT = 30.0
22
+ MAX_RETRIES = 3
23
+ RETRY_STATUSES = {429, 500, 502, 503, 504}
24
+
25
+
26
+ def _build_headers(api_key: str, api_secret: str | None = None) -> dict[str, str]:
27
+ headers = {
28
+ "X-API-Key": api_key,
29
+ "Content-Type": "application/json",
30
+ "User-Agent": "decane-python/0.8.0",
31
+ }
32
+ if api_secret:
33
+ headers["X-API-Secret"] = api_secret
34
+ return headers
35
+
36
+
37
+ def _with_idempotency(kwargs: dict[str, Any], key: str | None) -> dict[str, Any]:
38
+ """Return kwargs with an Idempotency-Key header (generated if not given)."""
39
+ headers = dict(kwargs.get("headers") or {})
40
+ headers["Idempotency-Key"] = key or str(uuid.uuid4())
41
+ return {**kwargs, "headers": headers}
42
+
43
+
44
+ def _raise_for_status(response: httpx.Response) -> None:
45
+ """Raise typed SDK errors from HTTP responses."""
46
+ if response.status_code < 400:
47
+ return
48
+
49
+ try:
50
+ body = response.json()
51
+ except Exception:
52
+ body = {"detail": response.text}
53
+
54
+ message = body.get("detail", body.get("message", body.get("error", str(body))))
55
+ code = body.get("code")
56
+
57
+ if response.status_code == 401:
58
+ raise AuthenticationError(message)
59
+ if response.status_code == 404:
60
+ raise NotFoundError(message)
61
+ if response.status_code == 422:
62
+ raise ValidationError(message, errors=body.get("errors"))
63
+ if response.status_code == 429:
64
+ retry_after = response.headers.get("Retry-After")
65
+ raise RateLimitError(message, retry_after=float(retry_after) if retry_after else None)
66
+ if response.status_code >= 500:
67
+ raise ServerError(message)
68
+
69
+ raise DecaneError(message, code=code, status=response.status_code)
70
+
71
+
72
+ class SyncHTTP:
73
+ """Synchronous HTTP client with retry logic."""
74
+
75
+ def __init__(self, api_key: str, base_url: str, timeout: float, api_secret: str | None = None):
76
+ self._client = httpx.Client(
77
+ base_url=base_url,
78
+ headers=_build_headers(api_key, api_secret),
79
+ timeout=timeout,
80
+ )
81
+
82
+ def request(self, method: str, path: str, **kwargs: Any) -> Any:
83
+ last_exc: Exception | None = None
84
+ for attempt in range(MAX_RETRIES):
85
+ try:
86
+ response = self._client.request(method, path, **kwargs)
87
+ if response.status_code in RETRY_STATUSES and attempt < MAX_RETRIES - 1:
88
+ wait = (2**attempt) * 0.5
89
+ if response.status_code == 429:
90
+ retry_after = response.headers.get("Retry-After")
91
+ if retry_after:
92
+ wait = float(retry_after)
93
+ time.sleep(wait)
94
+ continue
95
+ _raise_for_status(response)
96
+ if response.status_code == 204:
97
+ return None
98
+ return response.json()
99
+ except (httpx.ConnectError, httpx.TimeoutException) as e:
100
+ last_exc = e
101
+ if attempt < MAX_RETRIES - 1:
102
+ time.sleep((2**attempt) * 0.5)
103
+ continue
104
+ raise DecaneError(f"Connection error: {e}", code="network_error") from e
105
+ if last_exc:
106
+ raise DecaneError(f"Max retries exceeded: {last_exc}", code="network_error") from last_exc
107
+ raise DecaneError("Unexpected error", code="unknown")
108
+
109
+ def get(self, path: str, **kwargs: Any) -> Any:
110
+ return self.request("GET", path, **kwargs)
111
+
112
+ def post(self, path: str, *, idempotency_key: str | None = None, **kwargs: Any) -> Any:
113
+ if idempotency_key is not None:
114
+ kwargs = _with_idempotency(kwargs, idempotency_key)
115
+ return self.request("POST", path, **kwargs)
116
+
117
+ def post_idempotent(self, path: str, *, idempotency_key: str | None = None, **kwargs: Any) -> Any:
118
+ """POST with an Idempotency-Key header (random UUID if none given)."""
119
+ return self.request("POST", path, **_with_idempotency(kwargs, idempotency_key))
120
+
121
+ def put(self, path: str, **kwargs: Any) -> Any:
122
+ return self.request("PUT", path, **kwargs)
123
+
124
+ def patch(self, path: str, **kwargs: Any) -> Any:
125
+ return self.request("PATCH", path, **kwargs)
126
+
127
+ def delete(self, path: str, **kwargs: Any) -> Any:
128
+ return self.request("DELETE", path, **kwargs)
129
+
130
+ def close(self) -> None:
131
+ self._client.close()
132
+
133
+
134
+ class AsyncHTTP:
135
+ """Async HTTP client with retry logic."""
136
+
137
+ def __init__(self, api_key: str, base_url: str, timeout: float, api_secret: str | None = None):
138
+ self._client = httpx.AsyncClient(
139
+ base_url=base_url,
140
+ headers=_build_headers(api_key, api_secret),
141
+ timeout=timeout,
142
+ )
143
+
144
+ async def request(self, method: str, path: str, **kwargs: Any) -> Any:
145
+ import asyncio
146
+
147
+ last_exc: Exception | None = None
148
+ for attempt in range(MAX_RETRIES):
149
+ try:
150
+ response = await self._client.request(method, path, **kwargs)
151
+ if response.status_code in RETRY_STATUSES and attempt < MAX_RETRIES - 1:
152
+ wait = (2**attempt) * 0.5
153
+ if response.status_code == 429:
154
+ retry_after = response.headers.get("Retry-After")
155
+ if retry_after:
156
+ wait = float(retry_after)
157
+ await asyncio.sleep(wait)
158
+ continue
159
+ _raise_for_status(response)
160
+ if response.status_code == 204:
161
+ return None
162
+ return response.json()
163
+ except (httpx.ConnectError, httpx.TimeoutException) as e:
164
+ last_exc = e
165
+ if attempt < MAX_RETRIES - 1:
166
+ await asyncio.sleep((2**attempt) * 0.5)
167
+ continue
168
+ raise DecaneError(f"Connection error: {e}", code="network_error") from e
169
+ if last_exc:
170
+ raise DecaneError(f"Max retries exceeded: {last_exc}", code="network_error") from last_exc
171
+ raise DecaneError("Unexpected error", code="unknown")
172
+
173
+ async def get(self, path: str, **kwargs: Any) -> Any:
174
+ return await self.request("GET", path, **kwargs)
175
+
176
+ async def post(self, path: str, *, idempotency_key: str | None = None, **kwargs: Any) -> Any:
177
+ if idempotency_key is not None:
178
+ kwargs = _with_idempotency(kwargs, idempotency_key)
179
+ return await self.request("POST", path, **kwargs)
180
+
181
+ async def post_idempotent(self, path: str, *, idempotency_key: str | None = None, **kwargs: Any) -> Any:
182
+ """POST with an Idempotency-Key header (random UUID if none given)."""
183
+ return await self.request("POST", path, **_with_idempotency(kwargs, idempotency_key))
184
+
185
+ async def put(self, path: str, **kwargs: Any) -> Any:
186
+ return await self.request("PUT", path, **kwargs)
187
+
188
+ async def patch(self, path: str, **kwargs: Any) -> Any:
189
+ return await self.request("PATCH", path, **kwargs)
190
+
191
+ async def delete(self, path: str, **kwargs: Any) -> Any:
192
+ return await self.request("DELETE", path, **kwargs)
193
+
194
+ async def close(self) -> None:
195
+ await self._client.aclose()
decane/client.py ADDED
@@ -0,0 +1,122 @@
1
+ """Decane client — the main entry point for the SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._internal.http import AsyncHTTP, SyncHTTP, DEFAULT_BASE_URL, DEFAULT_TIMEOUT
6
+ from .resources.invoices import AsyncInvoices, Invoices
7
+ from .resources.payouts import AsyncPayouts, Payouts
8
+ from .resources.swaps import AsyncSwaps, Swaps
9
+ from .resources.wallets import AsyncWallets, Wallets
10
+ from .resources.webhooks import Webhooks
11
+ from .resources.payment_links import AsyncPaymentLinks, PaymentLinks
12
+ from .resources.subscriptions import AsyncSubscriptions, Subscriptions
13
+
14
+
15
+ class Decane:
16
+ """Synchronous Decane client.
17
+
18
+ Usage::
19
+
20
+ from decane import Decane
21
+
22
+ flow = Decane(api_key="gr_...", webhook_secret="gr_whsec_...")
23
+
24
+ # Create an invoice
25
+ invoice = flow.invoices.create(
26
+ amount=100, currency="USDT", network="tron",
27
+ )
28
+ print(invoice.wallet_address)
29
+
30
+ # Request a payout
31
+ payout = flow.payouts.create(
32
+ amount=50, currency="USDT", network="tron",
33
+ destination="TXyz...addr",
34
+ )
35
+
36
+ # Verify a webhook
37
+ event = flow.webhooks.verify(payload, signature)
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ api_key: str,
43
+ *,
44
+ api_secret: str | None = None,
45
+ webhook_secret: str | None = None,
46
+ base_url: str = DEFAULT_BASE_URL,
47
+ timeout: float = DEFAULT_TIMEOUT,
48
+ ):
49
+ if not api_key:
50
+ raise ValueError("api_key is required")
51
+
52
+ self._http = SyncHTTP(api_key=api_key, base_url=base_url, timeout=timeout, api_secret=api_secret)
53
+
54
+ self.invoices = Invoices(self._http)
55
+ self.payouts = Payouts(self._http)
56
+ self.wallets = Wallets(self._http)
57
+ self.swaps = Swaps(self._http)
58
+ self.webhooks = Webhooks(webhook_secret or "")
59
+ self.payment_links = PaymentLinks(self._http)
60
+ self.subscriptions = Subscriptions(self._http)
61
+
62
+ def close(self) -> None:
63
+ """Close the underlying HTTP client."""
64
+ self._http.close()
65
+
66
+ def __enter__(self) -> Decane:
67
+ return self
68
+
69
+ def __exit__(self, *args: object) -> None:
70
+ self.close()
71
+
72
+ def __repr__(self) -> str:
73
+ return "Decane(connected)"
74
+
75
+
76
+ class AsyncDecane:
77
+ """Async Decane client.
78
+
79
+ Usage::
80
+
81
+ from decane import AsyncDecane
82
+
83
+ async with AsyncDecane(api_key="gr_...") as flow:
84
+ invoice = await flow.invoices.create(
85
+ amount=100, currency="USDT", network="tron",
86
+ )
87
+ """
88
+
89
+ def __init__(
90
+ self,
91
+ api_key: str,
92
+ *,
93
+ api_secret: str | None = None,
94
+ webhook_secret: str | None = None,
95
+ base_url: str = DEFAULT_BASE_URL,
96
+ timeout: float = DEFAULT_TIMEOUT,
97
+ ):
98
+ if not api_key:
99
+ raise ValueError("api_key is required")
100
+
101
+ self._http = AsyncHTTP(api_key=api_key, base_url=base_url, timeout=timeout, api_secret=api_secret)
102
+
103
+ self.invoices = AsyncInvoices(self._http)
104
+ self.payouts = AsyncPayouts(self._http)
105
+ self.wallets = AsyncWallets(self._http)
106
+ self.swaps = AsyncSwaps(self._http)
107
+ self.webhooks = Webhooks(webhook_secret or "")
108
+ self.payment_links = AsyncPaymentLinks(self._http)
109
+ self.subscriptions = AsyncSubscriptions(self._http)
110
+
111
+ async def close(self) -> None:
112
+ """Close the underlying HTTP client."""
113
+ await self._http.close()
114
+
115
+ async def __aenter__(self) -> AsyncDecane:
116
+ return self
117
+
118
+ async def __aexit__(self, *args: object) -> None:
119
+ await self.close()
120
+
121
+ def __repr__(self) -> str:
122
+ return "AsyncDecane(connected)"
decane/errors.py ADDED
@@ -0,0 +1,60 @@
1
+ """Decane SDK error types."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class DecaneError(Exception):
7
+ """Base error for all Decane SDK errors."""
8
+
9
+ def __init__(self, message: str, code: str | None = None, status: int | None = None):
10
+ self.message = message
11
+ self.code = code
12
+ self.status = status
13
+ super().__init__(message)
14
+
15
+ def __repr__(self) -> str:
16
+ return f"{self.__class__.__name__}(message={self.message!r}, code={self.code!r}, status={self.status})"
17
+
18
+
19
+ class AuthenticationError(DecaneError):
20
+ """Invalid or missing API key."""
21
+
22
+ def __init__(self, message: str = "Invalid or missing API key"):
23
+ super().__init__(message, code="authentication_error", status=401)
24
+
25
+
26
+ class NotFoundError(DecaneError):
27
+ """Resource not found."""
28
+
29
+ def __init__(self, message: str = "Resource not found"):
30
+ super().__init__(message, code="not_found", status=404)
31
+
32
+
33
+ class ValidationError(DecaneError):
34
+ """Invalid request parameters."""
35
+
36
+ def __init__(self, message: str = "Validation error", errors: list[dict] | None = None):
37
+ self.errors = errors or []
38
+ super().__init__(message, code="validation_error", status=422)
39
+
40
+
41
+ class RateLimitError(DecaneError):
42
+ """Too many requests."""
43
+
44
+ def __init__(self, message: str = "Rate limit exceeded", retry_after: float | None = None):
45
+ self.retry_after = retry_after
46
+ super().__init__(message, code="rate_limit", status=429)
47
+
48
+
49
+ class ServerError(DecaneError):
50
+ """Server-side error."""
51
+
52
+ def __init__(self, message: str = "Internal server error"):
53
+ super().__init__(message, code="server_error", status=500)
54
+
55
+
56
+ class InvalidSignatureError(DecaneError):
57
+ """Webhook signature verification failed."""
58
+
59
+ def __init__(self, message: str = "Invalid webhook signature"):
60
+ super().__init__(message, code="invalid_signature", status=None)
@@ -0,0 +1,17 @@
1
+ from .invoices import Invoices, AsyncInvoices
2
+ from .payouts import Payouts, AsyncPayouts
3
+ from .wallets import Wallets, AsyncWallets
4
+ from .swaps import Swaps, AsyncSwaps
5
+ from .webhooks import Webhooks
6
+ from .payment_links import PaymentLinks, AsyncPaymentLinks
7
+ from .subscriptions import Subscriptions, AsyncSubscriptions
8
+
9
+ __all__ = [
10
+ "Invoices", "AsyncInvoices",
11
+ "Payouts", "AsyncPayouts",
12
+ "Wallets", "AsyncWallets",
13
+ "Swaps", "AsyncSwaps",
14
+ "Webhooks",
15
+ "PaymentLinks", "AsyncPaymentLinks",
16
+ "Subscriptions", "AsyncSubscriptions",
17
+ ]
@@ -0,0 +1,155 @@
1
+ """Invoice resource — create and manage payment invoices."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .._internal.http import AsyncHTTP, SyncHTTP
8
+ from ..types import Invoice, PaginatedList
9
+
10
+
11
+ class Invoices:
12
+ """Synchronous invoice operations."""
13
+
14
+ def __init__(self, http: SyncHTTP):
15
+ self._http = http
16
+
17
+ def create(
18
+ self,
19
+ amount: float,
20
+ currency: str = "USD",
21
+ *,
22
+ network: str | None = None,
23
+ crypto_currency: str | None = None,
24
+ merchant_order_id: str | None = None,
25
+ customer_email: str | None = None,
26
+ customer_name: str | None = None,
27
+ callback_url: str | None = None,
28
+ return_url: str | None = None,
29
+ metadata: dict[str, Any] | None = None,
30
+ idempotency_key: str | None = None,
31
+ ) -> Invoice:
32
+ """Create a payment invoice.
33
+
34
+ Omit ``network`` and ``crypto_currency`` to create a **flex invoice**
35
+ where the customer picks their preferred crypto on the payment page.
36
+ Supply both to lock the rate at creation time (fixed invoice).
37
+
38
+ ``idempotency_key`` is sent as the ``Idempotency-Key`` header. If you
39
+ omit it, a random key is generated so automatic retries cannot create
40
+ a second invoice. Pass your own key to make retries from your side safe.
41
+ """
42
+ payload: dict[str, Any] = {"amount": amount, "currency": currency}
43
+ if network is not None:
44
+ payload["network"] = network
45
+ if crypto_currency is not None:
46
+ payload["crypto_currency"] = crypto_currency
47
+ if merchant_order_id is not None:
48
+ payload["merchant_order_id"] = merchant_order_id
49
+ if customer_email is not None:
50
+ payload["customer_email"] = customer_email
51
+ if customer_name is not None:
52
+ payload["customer_name"] = customer_name
53
+ if callback_url is not None:
54
+ payload["callback_url"] = callback_url
55
+ if return_url is not None:
56
+ payload["return_url"] = return_url
57
+ if metadata is not None:
58
+ payload["metadata"] = metadata
59
+
60
+ data = self._http.post_idempotent("/invoices", json=payload, idempotency_key=idempotency_key)
61
+ return Invoice.from_dict(data)
62
+
63
+ def get(self, invoice_id: str) -> Invoice:
64
+ """Get an invoice by ID."""
65
+ data = self._http.get(f"/invoices/{invoice_id}")
66
+ return Invoice.from_dict(data)
67
+
68
+ def list(
69
+ self,
70
+ *,
71
+ status: str | None = None,
72
+ currency: str | None = None,
73
+ page: int = 1,
74
+ limit: int = 50,
75
+ ) -> PaginatedList:
76
+ """List invoices with optional filters."""
77
+ params: dict[str, Any] = {"page": page, "limit": limit}
78
+ if status:
79
+ params["status"] = status
80
+ if currency:
81
+ params["currency"] = currency
82
+ data = self._http.get("/invoices", params=params)
83
+ return PaginatedList.from_dict(data, Invoice)
84
+
85
+ def cancel(self, invoice_id: str) -> Invoice:
86
+ """Cancel a pending invoice."""
87
+ data = self._http.post(f"/invoices/{invoice_id}/cancel")
88
+ return Invoice.from_dict(data)
89
+
90
+
91
+ class AsyncInvoices:
92
+ """Async invoice operations."""
93
+
94
+ def __init__(self, http: AsyncHTTP):
95
+ self._http = http
96
+
97
+ async def create(
98
+ self,
99
+ amount: float,
100
+ currency: str = "USD",
101
+ *,
102
+ network: str | None = None,
103
+ crypto_currency: str | None = None,
104
+ merchant_order_id: str | None = None,
105
+ customer_email: str | None = None,
106
+ customer_name: str | None = None,
107
+ callback_url: str | None = None,
108
+ return_url: str | None = None,
109
+ metadata: dict[str, Any] | None = None,
110
+ idempotency_key: str | None = None,
111
+ ) -> Invoice:
112
+ payload: dict[str, Any] = {"amount": amount, "currency": currency}
113
+ if network is not None:
114
+ payload["network"] = network
115
+ if crypto_currency is not None:
116
+ payload["crypto_currency"] = crypto_currency
117
+ if merchant_order_id is not None:
118
+ payload["merchant_order_id"] = merchant_order_id
119
+ if customer_email is not None:
120
+ payload["customer_email"] = customer_email
121
+ if customer_name is not None:
122
+ payload["customer_name"] = customer_name
123
+ if callback_url is not None:
124
+ payload["callback_url"] = callback_url
125
+ if return_url is not None:
126
+ payload["return_url"] = return_url
127
+ if metadata is not None:
128
+ payload["metadata"] = metadata
129
+
130
+ data = await self._http.post_idempotent("/invoices", json=payload, idempotency_key=idempotency_key)
131
+ return Invoice.from_dict(data)
132
+
133
+ async def get(self, invoice_id: str) -> Invoice:
134
+ data = await self._http.get(f"/invoices/{invoice_id}")
135
+ return Invoice.from_dict(data)
136
+
137
+ async def list(
138
+ self,
139
+ *,
140
+ status: str | None = None,
141
+ currency: str | None = None,
142
+ page: int = 1,
143
+ limit: int = 50,
144
+ ) -> PaginatedList:
145
+ params: dict[str, Any] = {"page": page, "limit": limit}
146
+ if status:
147
+ params["status"] = status
148
+ if currency:
149
+ params["currency"] = currency
150
+ data = await self._http.get("/invoices", params=params)
151
+ return PaginatedList.from_dict(data, Invoice)
152
+
153
+ async def cancel(self, invoice_id: str) -> Invoice:
154
+ data = await self._http.post(f"/invoices/{invoice_id}/cancel")
155
+ return Invoice.from_dict(data)
@@ -0,0 +1,81 @@
1
+ """Payment link resource — create and manage hosted payment links."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .._internal.http import AsyncHTTP, SyncHTTP
8
+
9
+
10
+ class PaymentLinks:
11
+ """Synchronous payment link operations."""
12
+
13
+ def __init__(self, http: SyncHTTP):
14
+ self._http = http
15
+
16
+ def create(
17
+ self,
18
+ amount: float,
19
+ currency: str,
20
+ *,
21
+ title: str | None = None,
22
+ description: str | None = None,
23
+ redirect_url: str | None = None,
24
+ metadata: dict[str, Any] | None = None,
25
+ ) -> dict[str, Any]:
26
+ payload: dict[str, Any] = {"amount": amount, "currency": currency}
27
+ if title is not None:
28
+ payload["title"] = title
29
+ if description is not None:
30
+ payload["description"] = description
31
+ if redirect_url is not None:
32
+ payload["redirect_url"] = redirect_url
33
+ if metadata is not None:
34
+ payload["metadata"] = metadata
35
+ return self._http.post("/merchants/me/payment-links", json=payload)
36
+
37
+ def list(self, *, page: int = 1, limit: int = 50) -> list[dict[str, Any]]:
38
+ return self._http.get("/merchants/me/payment-links", params={"page": page, "limit": limit})
39
+
40
+ def update(self, link_id: str, **fields: Any) -> dict[str, Any]:
41
+ return self._http.patch(f"/merchants/me/payment-links/{link_id}", json=fields)
42
+
43
+ def delete(self, link_id: str) -> None:
44
+ self._http.delete(f"/merchants/me/payment-links/{link_id}")
45
+
46
+
47
+ class AsyncPaymentLinks:
48
+ """Async payment link operations."""
49
+
50
+ def __init__(self, http: AsyncHTTP):
51
+ self._http = http
52
+
53
+ async def create(
54
+ self,
55
+ amount: float,
56
+ currency: str,
57
+ *,
58
+ title: str | None = None,
59
+ description: str | None = None,
60
+ redirect_url: str | None = None,
61
+ metadata: dict[str, Any] | None = None,
62
+ ) -> dict[str, Any]:
63
+ payload: dict[str, Any] = {"amount": amount, "currency": currency}
64
+ if title is not None:
65
+ payload["title"] = title
66
+ if description is not None:
67
+ payload["description"] = description
68
+ if redirect_url is not None:
69
+ payload["redirect_url"] = redirect_url
70
+ if metadata is not None:
71
+ payload["metadata"] = metadata
72
+ return await self._http.post("/merchants/me/payment-links", json=payload)
73
+
74
+ async def list(self, *, page: int = 1, limit: int = 50) -> list[dict[str, Any]]:
75
+ return await self._http.get("/merchants/me/payment-links", params={"page": page, "limit": limit})
76
+
77
+ async def update(self, link_id: str, **fields: Any) -> dict[str, Any]:
78
+ return await self._http.patch(f"/merchants/me/payment-links/{link_id}", json=fields)
79
+
80
+ async def delete(self, link_id: str) -> None:
81
+ await self._http.delete(f"/merchants/me/payment-links/{link_id}")