integrateledger 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.
- integrateledger-0.1.0/LICENSE +21 -0
- integrateledger-0.1.0/MANIFEST.in +9 -0
- integrateledger-0.1.0/PKG-INFO +54 -0
- integrateledger-0.1.0/README.md +34 -0
- integrateledger-0.1.0/integrateledger/__init__.py +9 -0
- integrateledger-0.1.0/integrateledger.egg-info/PKG-INFO +54 -0
- integrateledger-0.1.0/integrateledger.egg-info/SOURCES.txt +12 -0
- integrateledger-0.1.0/integrateledger.egg-info/dependency_links.txt +1 -0
- integrateledger-0.1.0/integrateledger.egg-info/top_level.txt +2 -0
- integrateledger-0.1.0/ledger_sdk/__init__.py +5 -0
- integrateledger-0.1.0/ledger_sdk/client.py +216 -0
- integrateledger-0.1.0/pyproject.toml +29 -0
- integrateledger-0.1.0/setup.cfg +4 -0
- integrateledger-0.1.0/setup.py +5 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ledger
|
|
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
|
|
13
|
+
all 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.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: integrateledger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Ledger billing and usage API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Documentation, https://docs.integrateledger.com/python-sdk
|
|
7
|
+
Project-URL: Homepage, https://docs.integrateledger.com
|
|
8
|
+
Keywords: ledger,billing,usage,credits,sdk
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# integrateledger
|
|
22
|
+
|
|
23
|
+
The server-side Python SDK for Ledger's billing and usage API. Install it with
|
|
24
|
+
`python3 -m pip install integrateledger`, then use
|
|
25
|
+
`from integrateledger import ledger`. The capitalized `Ledger` class and
|
|
26
|
+
`ledger_sdk` import remain available for existing source users. See the
|
|
27
|
+
[Ledger docs](https://docs.integrateledger.com/python-sdk) for examples.
|
|
28
|
+
|
|
29
|
+
From a repository checkout, run the SDK tests with:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 -m pip install ./packages/sdk-python
|
|
33
|
+
python3 -m unittest discover -s packages/sdk-python/tests -v
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import os
|
|
38
|
+
from integrateledger import ledger
|
|
39
|
+
|
|
40
|
+
client = ledger(api_key=os.environ["LEDGER_API_KEY"])
|
|
41
|
+
client.create_feature(id="api_call", name="API call", credit_cost=1)
|
|
42
|
+
client.create_plan(id="starter", name="Starter", monthly_credits=100, monthly_price_cents=1900)
|
|
43
|
+
client.create_add_on(id="extra_credits", name="Extra credits", credit_amount=50, price_cents=500)
|
|
44
|
+
client.create_or_get_customer(customer_id="buyer_123")
|
|
45
|
+
customer = client.get_customer(customer_id="buyer_123")
|
|
46
|
+
subscriptions = client.list_customer_subscriptions(customer_id="buyer_123")
|
|
47
|
+
purchases = client.list_customer_purchases(customer_id="buyer_123")
|
|
48
|
+
# Use an active paid subscription ID from the subscriptions list.
|
|
49
|
+
client.set_subscription_cancellation(customer_id="buyer_123", subscription_id=42, cancel_at_period_end=True)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Methods return response dictionaries and use Python `snake_case` arguments. The client maps them to Ledger's JSON contract and sends the organization key in the `Authorization` header. Pass `base_url="http://127.0.0.1:3001"` for a local API.
|
|
53
|
+
|
|
54
|
+
Use a `ledger_test_` key for Sandbox or a `ledger_live_` key for Live. The key chooses the environment; the client methods and API URL stay the same. Store these keys in separate server-side variables, and complete sandbox validation before enabling live payments.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# integrateledger
|
|
2
|
+
|
|
3
|
+
The server-side Python SDK for Ledger's billing and usage API. Install it with
|
|
4
|
+
`python3 -m pip install integrateledger`, then use
|
|
5
|
+
`from integrateledger import ledger`. The capitalized `Ledger` class and
|
|
6
|
+
`ledger_sdk` import remain available for existing source users. See the
|
|
7
|
+
[Ledger docs](https://docs.integrateledger.com/python-sdk) for examples.
|
|
8
|
+
|
|
9
|
+
From a repository checkout, run the SDK tests with:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python3 -m pip install ./packages/sdk-python
|
|
13
|
+
python3 -m unittest discover -s packages/sdk-python/tests -v
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import os
|
|
18
|
+
from integrateledger import ledger
|
|
19
|
+
|
|
20
|
+
client = ledger(api_key=os.environ["LEDGER_API_KEY"])
|
|
21
|
+
client.create_feature(id="api_call", name="API call", credit_cost=1)
|
|
22
|
+
client.create_plan(id="starter", name="Starter", monthly_credits=100, monthly_price_cents=1900)
|
|
23
|
+
client.create_add_on(id="extra_credits", name="Extra credits", credit_amount=50, price_cents=500)
|
|
24
|
+
client.create_or_get_customer(customer_id="buyer_123")
|
|
25
|
+
customer = client.get_customer(customer_id="buyer_123")
|
|
26
|
+
subscriptions = client.list_customer_subscriptions(customer_id="buyer_123")
|
|
27
|
+
purchases = client.list_customer_purchases(customer_id="buyer_123")
|
|
28
|
+
# Use an active paid subscription ID from the subscriptions list.
|
|
29
|
+
client.set_subscription_cancellation(customer_id="buyer_123", subscription_id=42, cancel_at_period_end=True)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Methods return response dictionaries and use Python `snake_case` arguments. The client maps them to Ledger's JSON contract and sends the organization key in the `Authorization` header. Pass `base_url="http://127.0.0.1:3001"` for a local API.
|
|
33
|
+
|
|
34
|
+
Use a `ledger_test_` key for Sandbox or a `ledger_live_` key for Live. The key chooses the environment; the client methods and API URL stay the same. Store these keys in separate server-side variables, and complete sandbox validation before enabling live payments.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Public Python import for the Ledger client."""
|
|
2
|
+
|
|
3
|
+
from ledger_sdk import Ledger, LedgerAPIError, LedgerConnectionError
|
|
4
|
+
|
|
5
|
+
# Keep the requested short constructor spelling while retaining the existing
|
|
6
|
+
# capitalized class and ledger_sdk imports for existing source consumers.
|
|
7
|
+
ledger = Ledger
|
|
8
|
+
|
|
9
|
+
__all__ = ["ledger", "Ledger", "LedgerAPIError", "LedgerConnectionError"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: integrateledger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Ledger billing and usage API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Documentation, https://docs.integrateledger.com/python-sdk
|
|
7
|
+
Project-URL: Homepage, https://docs.integrateledger.com
|
|
8
|
+
Keywords: ledger,billing,usage,credits,sdk
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# integrateledger
|
|
22
|
+
|
|
23
|
+
The server-side Python SDK for Ledger's billing and usage API. Install it with
|
|
24
|
+
`python3 -m pip install integrateledger`, then use
|
|
25
|
+
`from integrateledger import ledger`. The capitalized `Ledger` class and
|
|
26
|
+
`ledger_sdk` import remain available for existing source users. See the
|
|
27
|
+
[Ledger docs](https://docs.integrateledger.com/python-sdk) for examples.
|
|
28
|
+
|
|
29
|
+
From a repository checkout, run the SDK tests with:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 -m pip install ./packages/sdk-python
|
|
33
|
+
python3 -m unittest discover -s packages/sdk-python/tests -v
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import os
|
|
38
|
+
from integrateledger import ledger
|
|
39
|
+
|
|
40
|
+
client = ledger(api_key=os.environ["LEDGER_API_KEY"])
|
|
41
|
+
client.create_feature(id="api_call", name="API call", credit_cost=1)
|
|
42
|
+
client.create_plan(id="starter", name="Starter", monthly_credits=100, monthly_price_cents=1900)
|
|
43
|
+
client.create_add_on(id="extra_credits", name="Extra credits", credit_amount=50, price_cents=500)
|
|
44
|
+
client.create_or_get_customer(customer_id="buyer_123")
|
|
45
|
+
customer = client.get_customer(customer_id="buyer_123")
|
|
46
|
+
subscriptions = client.list_customer_subscriptions(customer_id="buyer_123")
|
|
47
|
+
purchases = client.list_customer_purchases(customer_id="buyer_123")
|
|
48
|
+
# Use an active paid subscription ID from the subscriptions list.
|
|
49
|
+
client.set_subscription_cancellation(customer_id="buyer_123", subscription_id=42, cancel_at_period_end=True)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Methods return response dictionaries and use Python `snake_case` arguments. The client maps them to Ledger's JSON contract and sends the organization key in the `Authorization` header. Pass `base_url="http://127.0.0.1:3001"` for a local API.
|
|
53
|
+
|
|
54
|
+
Use a `ledger_test_` key for Sandbox or a `ledger_live_` key for Live. The key chooses the environment; the client methods and API URL stay the same. Store these keys in separate server-side variables, and complete sandbox validation before enabling live payments.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
integrateledger/__init__.py
|
|
7
|
+
integrateledger.egg-info/PKG-INFO
|
|
8
|
+
integrateledger.egg-info/SOURCES.txt
|
|
9
|
+
integrateledger.egg-info/dependency_links.txt
|
|
10
|
+
integrateledger.egg-info/top_level.txt
|
|
11
|
+
ledger_sdk/__init__.py
|
|
12
|
+
ledger_sdk/client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Small, dependency-free client for Ledger's public HTTP API.
|
|
2
|
+
|
|
3
|
+
All method arguments use Python snake_case; JSON sent to Ledger uses the API's
|
|
4
|
+
documented camelCase fields (and request_id for usage tracking).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any, Dict, Mapping, Optional
|
|
11
|
+
from urllib.error import HTTPError, URLError
|
|
12
|
+
from urllib.parse import quote
|
|
13
|
+
from urllib.request import Request, urlopen
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LedgerAPIError(Exception):
|
|
17
|
+
"""An HTTP error returned by Ledger."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, status: int, code: str, body: Any):
|
|
20
|
+
self.status = status
|
|
21
|
+
self.code = code
|
|
22
|
+
self.body = body
|
|
23
|
+
super().__init__(f"Ledger API returned {status}: {code}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LedgerConnectionError(Exception):
|
|
27
|
+
"""Ledger could not be reached before a response was received."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Ledger:
|
|
31
|
+
"""Organization-scoped client. Keep ``api_key`` on your server."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
api_key: str,
|
|
37
|
+
base_url: str = "https://api.integrateledger.com",
|
|
38
|
+
timeout: float = 30.0,
|
|
39
|
+
):
|
|
40
|
+
if not api_key:
|
|
41
|
+
raise ValueError("api_key is required")
|
|
42
|
+
if not base_url.startswith(("https://", "http://")):
|
|
43
|
+
raise ValueError("base_url must be an HTTP URL")
|
|
44
|
+
if timeout <= 0:
|
|
45
|
+
raise ValueError("timeout must be positive")
|
|
46
|
+
self.api_key = api_key
|
|
47
|
+
self.base_url = base_url.rstrip("/")
|
|
48
|
+
self.timeout = timeout
|
|
49
|
+
|
|
50
|
+
def _request(
|
|
51
|
+
self,
|
|
52
|
+
method: str,
|
|
53
|
+
path: str,
|
|
54
|
+
body: Optional[Mapping[str, Any]] = None,
|
|
55
|
+
*,
|
|
56
|
+
idempotency_key: Optional[str] = None,
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
headers = {
|
|
59
|
+
"Accept": "application/json",
|
|
60
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
61
|
+
}
|
|
62
|
+
data = None
|
|
63
|
+
if body is not None:
|
|
64
|
+
headers["Content-Type"] = "application/json"
|
|
65
|
+
data = json.dumps(body, separators=(",", ":")).encode("utf-8")
|
|
66
|
+
if idempotency_key is not None:
|
|
67
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
68
|
+
request = Request(self.base_url + path, data=data, headers=headers, method=method)
|
|
69
|
+
try:
|
|
70
|
+
with urlopen(request, timeout=self.timeout) as response:
|
|
71
|
+
payload = response.read()
|
|
72
|
+
return json.loads(payload) if payload else {}
|
|
73
|
+
except HTTPError as error:
|
|
74
|
+
payload = error.read()
|
|
75
|
+
try:
|
|
76
|
+
details = json.loads(payload) if payload else {}
|
|
77
|
+
except (ValueError, TypeError):
|
|
78
|
+
details = {"error": payload.decode("utf-8", "replace")}
|
|
79
|
+
code = details.get("error", "http_error") if isinstance(details, dict) else "http_error"
|
|
80
|
+
raise LedgerAPIError(error.code, code, details) from error
|
|
81
|
+
except URLError as error:
|
|
82
|
+
raise LedgerConnectionError(str(error.reason)) from error
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _item_path(kind: str, item_id: str) -> str:
|
|
86
|
+
return f"/v1/{kind}/{quote(item_id, safe='')}"
|
|
87
|
+
|
|
88
|
+
# Catalog
|
|
89
|
+
def publish_catalog(self, catalog: Mapping[str, Any]) -> Dict[str, Any]:
|
|
90
|
+
return self._request("PUT", "/v1/catalog", catalog)
|
|
91
|
+
|
|
92
|
+
def list_plans(self) -> Dict[str, Any]:
|
|
93
|
+
return self._request("GET", "/v1/plans")
|
|
94
|
+
|
|
95
|
+
def create_plan(self, *, id: str, name: str, monthly_credits: int, monthly_price_cents: int) -> Dict[str, Any]:
|
|
96
|
+
return self._request("POST", "/v1/plans", {
|
|
97
|
+
"id": id, "name": name, "monthlyCredits": monthly_credits,
|
|
98
|
+
"monthlyPriceCents": monthly_price_cents,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
def get_plan(self, id: str) -> Dict[str, Any]:
|
|
102
|
+
return self._request("GET", self._item_path("plans", id))
|
|
103
|
+
|
|
104
|
+
def update_plan(self, id: str, *, name: str, monthly_credits: int, monthly_price_cents: int) -> Dict[str, Any]:
|
|
105
|
+
return self._request("PUT", self._item_path("plans", id), {
|
|
106
|
+
"name": name, "monthlyCredits": monthly_credits,
|
|
107
|
+
"monthlyPriceCents": monthly_price_cents,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
def archive_plan(self, id: str) -> Dict[str, Any]:
|
|
111
|
+
return self._request("DELETE", self._item_path("plans", id))
|
|
112
|
+
|
|
113
|
+
def list_features(self) -> Dict[str, Any]:
|
|
114
|
+
return self._request("GET", "/v1/features")
|
|
115
|
+
|
|
116
|
+
def create_feature(self, *, id: str, name: str, credit_cost: int) -> Dict[str, Any]:
|
|
117
|
+
return self._request("POST", "/v1/features", {"id": id, "name": name, "creditCost": credit_cost})
|
|
118
|
+
|
|
119
|
+
def get_feature(self, id: str) -> Dict[str, Any]:
|
|
120
|
+
return self._request("GET", self._item_path("features", id))
|
|
121
|
+
|
|
122
|
+
def update_feature(self, id: str, *, name: str, credit_cost: int) -> Dict[str, Any]:
|
|
123
|
+
return self._request("PUT", self._item_path("features", id), {"name": name, "creditCost": credit_cost})
|
|
124
|
+
|
|
125
|
+
def archive_feature(self, id: str) -> Dict[str, Any]:
|
|
126
|
+
return self._request("DELETE", self._item_path("features", id))
|
|
127
|
+
|
|
128
|
+
def list_add_ons(self) -> Dict[str, Any]:
|
|
129
|
+
return self._request("GET", "/v1/add-ons")
|
|
130
|
+
|
|
131
|
+
def create_add_on(self, *, id: str, name: str, credit_amount: int, price_cents: int) -> Dict[str, Any]:
|
|
132
|
+
return self._request("POST", "/v1/add-ons", {
|
|
133
|
+
"id": id, "name": name, "type": "credit_pack",
|
|
134
|
+
"creditAmount": credit_amount, "priceCents": price_cents,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
def get_add_on(self, id: str) -> Dict[str, Any]:
|
|
138
|
+
return self._request("GET", self._item_path("add-ons", id))
|
|
139
|
+
|
|
140
|
+
def update_add_on(self, id: str, *, name: str, credit_amount: int, price_cents: int) -> Dict[str, Any]:
|
|
141
|
+
return self._request("PUT", self._item_path("add-ons", id), {
|
|
142
|
+
"name": name, "type": "credit_pack", "creditAmount": credit_amount,
|
|
143
|
+
"priceCents": price_cents,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
def archive_add_on(self, id: str) -> Dict[str, Any]:
|
|
147
|
+
return self._request("DELETE", self._item_path("add-ons", id))
|
|
148
|
+
|
|
149
|
+
# Customers, usage, and billing
|
|
150
|
+
def create_or_get_customer(self, *, customer_id: str) -> Dict[str, Any]:
|
|
151
|
+
return self._request("POST", "/v1/customers", {"customerId": customer_id})
|
|
152
|
+
|
|
153
|
+
def get_customer(self, *, customer_id: str) -> Dict[str, Any]:
|
|
154
|
+
return self._request("GET", f"/v1/customers?customerId={quote(customer_id, safe='')}")
|
|
155
|
+
|
|
156
|
+
def list_customer_subscriptions(
|
|
157
|
+
self, *, customer_id: str, limit: Optional[int] = None, cursor: Optional[int] = None
|
|
158
|
+
) -> Dict[str, Any]:
|
|
159
|
+
return self._request("GET", self._customer_history_path("subscriptions", customer_id, limit, cursor))
|
|
160
|
+
|
|
161
|
+
def list_customer_purchases(
|
|
162
|
+
self, *, customer_id: str, limit: Optional[int] = None, cursor: Optional[int] = None
|
|
163
|
+
) -> Dict[str, Any]:
|
|
164
|
+
return self._request("GET", self._customer_history_path("purchases", customer_id, limit, cursor))
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def _customer_history_path(kind: str, customer_id: str, limit: Optional[int], cursor: Optional[int]) -> str:
|
|
168
|
+
if limit is not None and (not isinstance(limit, int) or isinstance(limit, bool) or not 1 <= limit <= 100):
|
|
169
|
+
raise ValueError("limit must be an integer from 1 to 100")
|
|
170
|
+
if cursor is not None and (not isinstance(cursor, int) or isinstance(cursor, bool) or cursor < 1):
|
|
171
|
+
raise ValueError("cursor must be a positive integer")
|
|
172
|
+
path = f"/v1/customers/{kind}?customerId={quote(customer_id, safe='')}"
|
|
173
|
+
if limit is not None:
|
|
174
|
+
path += f"&limit={limit}"
|
|
175
|
+
if cursor is not None:
|
|
176
|
+
path += f"&cursor={cursor}"
|
|
177
|
+
return path
|
|
178
|
+
|
|
179
|
+
def set_subscription_cancellation(
|
|
180
|
+
self, *, customer_id: str, subscription_id: int, cancel_at_period_end: bool
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
if not isinstance(subscription_id, int) or isinstance(subscription_id, bool) or subscription_id < 1:
|
|
183
|
+
raise ValueError("subscription_id must be a positive integer")
|
|
184
|
+
if not isinstance(cancel_at_period_end, bool):
|
|
185
|
+
raise ValueError("cancel_at_period_end must be a boolean")
|
|
186
|
+
return self._request(
|
|
187
|
+
"PUT",
|
|
188
|
+
f"/v1/customers/subscriptions/{subscription_id}/cancellation"
|
|
189
|
+
f"?customerId={quote(customer_id, safe='')}",
|
|
190
|
+
{"cancelAtPeriodEnd": cancel_at_period_end},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def check_credits(self, *, customer_id: str, feature_id: str) -> Dict[str, Any]:
|
|
194
|
+
return self._request("POST", "/v1/check", {"customerId": customer_id, "featureId": feature_id})
|
|
195
|
+
|
|
196
|
+
def track_usage(self, *, customer_id: str, feature_id: str, request_id: str) -> Dict[str, Any]:
|
|
197
|
+
return self._request("POST", "/v1/track", {
|
|
198
|
+
"customerId": customer_id, "featureId": feature_id, "request_id": request_id,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
def attach(self, *, customer_id: str, plan_id: Optional[str] = None, add_on_id: Optional[str] = None) -> Dict[str, Any]:
|
|
202
|
+
if (plan_id is None) == (add_on_id is None):
|
|
203
|
+
raise ValueError("provide exactly one of plan_id or add_on_id")
|
|
204
|
+
body = {"customerId": customer_id}
|
|
205
|
+
if plan_id is not None:
|
|
206
|
+
body["planId"] = plan_id
|
|
207
|
+
else:
|
|
208
|
+
body["addOnId"] = add_on_id
|
|
209
|
+
return self._request("POST", "/v1/billing/attach", body)
|
|
210
|
+
|
|
211
|
+
def upgrade_paid_plan(self, *, customer_id: str, plan_id: str, idempotency_key: str) -> Dict[str, Any]:
|
|
212
|
+
if not idempotency_key:
|
|
213
|
+
raise ValueError("idempotency_key is required")
|
|
214
|
+
return self._request("POST", "/v1/billing/upgrade", {
|
|
215
|
+
"customerId": customer_id, "planId": plan_id,
|
|
216
|
+
}, idempotency_key=idempotency_key)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "integrateledger"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for the Ledger billing and usage API"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
keywords = ["ledger", "billing", "usage", "credits", "sdk"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.9",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Documentation = "https://docs.integrateledger.com/python-sdk"
|
|
26
|
+
Homepage = "https://docs.integrateledger.com"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
packages = ["ledger_sdk", "integrateledger"]
|