saman-api 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.
- saman_api-0.1.0/.gitignore +16 -0
- saman_api-0.1.0/CHANGELOG.md +10 -0
- saman_api-0.1.0/LICENSE +21 -0
- saman_api-0.1.0/PKG-INFO +242 -0
- saman_api-0.1.0/README.md +217 -0
- saman_api-0.1.0/docs/publishing.md +66 -0
- saman_api-0.1.0/docs/reference.md +734 -0
- saman_api-0.1.0/examples/async_reads.py +15 -0
- saman_api-0.1.0/examples/session_and_history.py +10 -0
- saman_api-0.1.0/pyproject.toml +41 -0
- saman_api-0.1.0/src/samanapi/__init__.py +36 -0
- saman_api-0.1.0/src/samanapi/__main__.py +3 -0
- saman_api-0.1.0/src/samanapi/_transport.py +188 -0
- saman_api-0.1.0/src/samanapi/async_client.py +433 -0
- saman_api-0.1.0/src/samanapi/cli.py +222 -0
- saman_api-0.1.0/src/samanapi/client.py +857 -0
- saman_api-0.1.0/src/samanapi/errors.py +94 -0
- saman_api-0.1.0/src/samanapi/py.typed +0 -0
- saman_api-0.1.0/src/samanapi/types.py +52 -0
- saman_api-0.1.0/tests/fake_service.cjs +16 -0
- saman_api-0.1.0/tests/test_client.py +147 -0
- saman_api-0.1.0/tests/test_contract.py +101 -0
- saman_api-0.1.0/tests/test_integration.py +64 -0
- saman_api-0.1.0/tests/test_workflows.py +237 -0
- saman_api-0.1.0/tests/typing_contract.py +16 -0
- saman_api-0.1.0/tools/audit_runtime.py +42 -0
- saman_api-0.1.0/tools/check_generated.py +23 -0
- saman_api-0.1.0/tools/generate_async.py +126 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial typed Python HTTP SDK for Saman Mobillet API 0.3+.
|
|
6
|
+
- Explicit sync and async methods for all 67 documented API operations.
|
|
7
|
+
- Interactive credential/SMS login, pending-session resume, and English CLI.
|
|
8
|
+
- Structured errors with preserved server payloads and no secret-bearing repr.
|
|
9
|
+
- Bounded transaction iteration, exact identifier preservation, no SDK retries.
|
|
10
|
+
- CLI commands for every operation, with explicit confirmation for money movement.
|
saman_api-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 APIBANK contributors
|
|
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.
|
saman_api-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: saman-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed sync and async Python clients for the Saman Mobillet HTTP API
|
|
5
|
+
Project-URL: Documentation, https://apibank.heydari.workers.dev/saman/
|
|
6
|
+
Project-URL: Repository, https://github.com/DevURANIUM/saman-api
|
|
7
|
+
Project-URL: Issues, https://github.com/DevURANIUM/saman-api/issues
|
|
8
|
+
Author: APIBANK contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api,banking,client,iran,mobillet,saman
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx<1,>=0.28.1
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# saman-api
|
|
27
|
+
|
|
28
|
+
A typed, synchronous and asynchronous Python client for the **Saman Mobillet HTTP API**.
|
|
29
|
+
|
|
30
|
+
This package talks to your running Saman API service. It **does not connect directly
|
|
31
|
+
to the bank**, replace the Node.js server, or execute requests through the documentation
|
|
32
|
+
website. The service manages bank encryption, cookies and the bank session.
|
|
33
|
+
|
|
34
|
+
Requires Python 3.10+ and API service 0.3+. All SDK prompts and messages are English.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pip install saman-api
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Until the first PyPI release, install from the source package directory:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Start the accompanying Node.js API server with `npm install` and `npm start`.
|
|
49
|
+
Configure `SAMAN_API_KEY` on the server and provide the same key to this client.
|
|
50
|
+
The default API URL is `http://127.0.0.1:8089/v1`; a supplied URL without `/v1`
|
|
51
|
+
has that suffix appended. Production connections should use HTTPS.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from samanapi import Client
|
|
55
|
+
|
|
56
|
+
with Client(base_url="http://127.0.0.1:8089/v1", api_key="YOUR_SERVICE_API_KEY") as client:
|
|
57
|
+
result = client.sign_in() # Username, hidden password, then hidden SMS code if needed
|
|
58
|
+
print(result["bankVerified"])
|
|
59
|
+
print(client.deposits())
|
|
60
|
+
print(client.cards())
|
|
61
|
+
print(client.balance())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The constructor also reads `SAMAN_API_URL` and `SAMAN_API_KEY`. Bank credentials
|
|
65
|
+
are not stored by this Python package. One API server owns one bank session:
|
|
66
|
+
creating another Python client does **not** create an independent bank login.
|
|
67
|
+
`close()` closes HTTP connections; `logout()` clears the shared server session.
|
|
68
|
+
|
|
69
|
+
## Login in a web application
|
|
70
|
+
|
|
71
|
+
Use explicit stages rather than an interactive prompt:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from samanapi import Client
|
|
75
|
+
|
|
76
|
+
with Client() as client:
|
|
77
|
+
result = client.login("YOUR_USERNAME", "YOUR_PASSWORD")
|
|
78
|
+
if result.get("otpRequired"):
|
|
79
|
+
if result.get("otpSent"):
|
|
80
|
+
# Collect the code in your application's UI, then call:
|
|
81
|
+
result = client.verify_otp("123456") # synthetic example code
|
|
82
|
+
else:
|
|
83
|
+
print(result.get("otpError"))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The service requests the device SMS automatically. Do not call `request_otp()`
|
|
87
|
+
after every login: it is an explicit resend. `sign_in()` accepts `otp_callback`
|
|
88
|
+
for integration, and resumes a pending code step when called without credentials.
|
|
89
|
+
Return `resend` from the callback to request another code. Wrong codes leave the
|
|
90
|
+
pending server session available for another attempt. Use `login(..., request_otp=False)`
|
|
91
|
+
only when you deliberately want the service's legacy manual-SMS workflow.
|
|
92
|
+
|
|
93
|
+
`ok=True` can mean that the initial credential step succeeded. A completed login
|
|
94
|
+
has `loggedIn=True` and `bankVerified=True`. Password-change requirements must be
|
|
95
|
+
completed in the bank app. Device-verification, card PIN2 and digital-rial activation
|
|
96
|
+
codes belong to different workflows.
|
|
97
|
+
|
|
98
|
+
## Async client
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import asyncio
|
|
102
|
+
from samanapi import AsyncClient
|
|
103
|
+
|
|
104
|
+
async def main():
|
|
105
|
+
async with AsyncClient() as client:
|
|
106
|
+
print(await client.check_session())
|
|
107
|
+
print(await client.latest_transactions("123-456-0000001-2", limit=10))
|
|
108
|
+
|
|
109
|
+
asyncio.run(main())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Every endpoint has the same name and parameters in `Client` and `AsyncClient`.
|
|
113
|
+
`AsyncClient.sign_in` accepts synchronous or asynchronous callbacks. Default
|
|
114
|
+
interactive callbacks run in a worker thread. Transaction iteration uses
|
|
115
|
+
`for row in client.iter_transactions(...)` or `async for` with the async client.
|
|
116
|
+
|
|
117
|
+
## History, details and exact search
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
with Client() as client:
|
|
121
|
+
history = client.transactions(
|
|
122
|
+
"123-456-0000001-2", page=0, size=20,
|
|
123
|
+
details=True, is_withdrawal=False,
|
|
124
|
+
from_date=1700000000, to_date=1700086400,
|
|
125
|
+
)
|
|
126
|
+
result = client.find_transaction(
|
|
127
|
+
"000000000001", "123-456-0000001-2",
|
|
128
|
+
match_by="reference", pages=10, max_transactions=200,
|
|
129
|
+
)
|
|
130
|
+
if not result["found"]:
|
|
131
|
+
print(result["searchComplete"], result["stopReason"])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Pages start at **0**. Transaction dates are Unix **seconds**; merchant summary
|
|
135
|
+
`from_date` is Unix **milliseconds**. Identifiers, tracking/reference numbers,
|
|
136
|
+
cards, deposit numbers and IBANs stay strings, preserving leading zeros.
|
|
137
|
+
Transaction IDs are encoded as one URL segment, including embedded slashes.
|
|
138
|
+
|
|
139
|
+
`details=True` lets the server include full bank detail/receipt objects in each
|
|
140
|
+
row's `detail`. Unknown fields are preserved. The SDK does not infer a sender's
|
|
141
|
+
name or card number from free text. JSON null balances remain `None`, and absent
|
|
142
|
+
fields remain absent; neither is converted to zero. Card and deposit balances
|
|
143
|
+
are not added together.
|
|
144
|
+
|
|
145
|
+
`iter_transactions(..., size=100, max_pages=10, details=False)` is bounded. It raises
|
|
146
|
+
`PaginationError` when a page repeats or the budget runs out before the end of
|
|
147
|
+
history. It does not silently claim that a bounded search covered the whole account.
|
|
148
|
+
|
|
149
|
+
## Transfers and digital rial
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
with Client() as client:
|
|
153
|
+
reasons = client.transfer_reasons()
|
|
154
|
+
owner = client.iban_owner("IR000000000000000000000000") # synthetic, replace it
|
|
155
|
+
# Programmatic money movement is explicit; no confirmation dialog is inserted:
|
|
156
|
+
# receipt = client.transfer_iban(source, destination, amount_irr, reason_code)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Transfer amounts are positive integer **IRR**, capped at JavaScript's safe integer
|
|
160
|
+
range. Card expiry is Jalali `YYYYMM`. `transfer_card` needs `cvv2`, `pin2`, and
|
|
161
|
+
`expire_date`. The server encrypts these fields. PAYA takes a deposit as source
|
|
162
|
+
and an IBAN as destination. Select `reason_code` from `transfer_reasons()`.
|
|
163
|
+
The current service implements CARD, DEPOSIT and PAYA, not SATNA/POL submission.
|
|
164
|
+
|
|
165
|
+
Digital rial has separate eligibility, SMS, activation, balance, transaction,
|
|
166
|
+
charge, discharge, deactivation and contact methods. Wallet history IDs differ
|
|
167
|
+
from deposit-history IDs. Activation currently supports CUSTOMER wallets.
|
|
168
|
+
|
|
169
|
+
The SDK never retries or follows redirects automatically. On a timeout or a
|
|
170
|
+
cancelled payment request, the operation may already have succeeded: inspect
|
|
171
|
+
history before submitting another instruction. API-server renewal behavior is
|
|
172
|
+
separate from the SDK; the companion server never automatically replays transfers.
|
|
173
|
+
|
|
174
|
+
## Errors and response preservation
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from samanapi import Client, ApiError, TransportError
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
with Client() as client:
|
|
181
|
+
print(client.cards())
|
|
182
|
+
except ApiError as exc:
|
|
183
|
+
print(exc.status_code, exc.code, exc.next_action)
|
|
184
|
+
# exc.payload contains the full response; inspect it only where appropriate.
|
|
185
|
+
except TransportError as exc:
|
|
186
|
+
print(exc.request_may_have_succeeded)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Exception types include `AuthenticationError`, `DeviceVerificationRequired`,
|
|
190
|
+
`PasswordChangeRequired`, `ValidationError`, `BankError`, `SmsDeliveryError`,
|
|
191
|
+
`TransportError`, `ProtocolError` and `PaginationError`. HTTP errors preserve
|
|
192
|
+
`payload`, `bank_status`, `auth`, `next_action`, `method` and `path`. Error string
|
|
193
|
+
representations do not print server payloads or credentials. An HTTP 200 body
|
|
194
|
+
containing an application `error` is not silently accepted.
|
|
195
|
+
|
|
196
|
+
The default response limit is 20 MiB; larger responses raise `ProtocolError`
|
|
197
|
+
rather than being truncated. Configure `max_response_bytes` explicitly if needed.
|
|
198
|
+
`timeout` defaults to 120 seconds. Longer bounded searches may need a larger value.
|
|
199
|
+
Custom HTTPX transports are supported for tests and advanced use; their own retry
|
|
200
|
+
or security behavior is the caller's responsibility. `trust_env` defaults to false.
|
|
201
|
+
|
|
202
|
+
## CLI
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
saman-api login
|
|
206
|
+
saman-api status
|
|
207
|
+
saman-api deposits
|
|
208
|
+
saman-api balance
|
|
209
|
+
saman-api transactions --deposit-number 123-456-0000001-2 --page 0 --size 20 --details false
|
|
210
|
+
saman-api find-transaction 000000000001 --deposit-number 123-456-0000001-2 --match-by reference
|
|
211
|
+
saman-api --help
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The API key is read from `SAMAN_API_KEY`. `SAMAN_USER` and `SAMAN_PASS` may supply
|
|
215
|
+
credentials to CLI login. All endpoint methods have hyphenated CLI commands;
|
|
216
|
+
`--help` lists their arguments. Boolean arguments take `true` or `false`.
|
|
217
|
+
Wallet filter lists are comma-separated. Use `python -m samanapi` as an
|
|
218
|
+
alternative entry point. The CLI writes JSON results to stdout and errors to
|
|
219
|
+
stderr; `--json-errors` gives structured errors and exit code 1. Cancellation
|
|
220
|
+
uses exit code 130. Financial commands require `TRANSFER`, wallet activation
|
|
221
|
+
`ACTIVATE`, and deactivation `DEACTIVATE`; noninteractive callers must pass the
|
|
222
|
+
matching `--confirm` value. Required card secrets are prompted without echo
|
|
223
|
+
when omitted. Use `import-har FILE` for a local private capture, never a public example.
|
|
224
|
+
|
|
225
|
+
## API reference and development
|
|
226
|
+
|
|
227
|
+
See [the complete method reference](docs/reference.md) and the
|
|
228
|
+
[HTTP service documentation](https://apibank.heydari.workers.dev/saman/).
|
|
229
|
+
`request("GET", "cards", query={...})` is available for future relative routes;
|
|
230
|
+
absolute/cross-origin paths and traversal are rejected before any I/O.
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
pip install -e .
|
|
234
|
+
python -m unittest discover -s tests -v
|
|
235
|
+
python tools/generate_async.py
|
|
236
|
+
python -m build
|
|
237
|
+
python -m twine check dist/*
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Tests use mock transports and a fake bank behind the real Node server. They do
|
|
241
|
+
not send money or verify a current live bank session. The source distribution
|
|
242
|
+
and wheel use explicit include rules and contain no sessions, captures or API keys.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# saman-api
|
|
2
|
+
|
|
3
|
+
A typed, synchronous and asynchronous Python client for the **Saman Mobillet HTTP API**.
|
|
4
|
+
|
|
5
|
+
This package talks to your running Saman API service. It **does not connect directly
|
|
6
|
+
to the bank**, replace the Node.js server, or execute requests through the documentation
|
|
7
|
+
website. The service manages bank encryption, cookies and the bank session.
|
|
8
|
+
|
|
9
|
+
Requires Python 3.10+ and API service 0.3+. All SDK prompts and messages are English.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pip install saman-api
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Until the first PyPI release, install from the source package directory:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pip install .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Start the accompanying Node.js API server with `npm install` and `npm start`.
|
|
24
|
+
Configure `SAMAN_API_KEY` on the server and provide the same key to this client.
|
|
25
|
+
The default API URL is `http://127.0.0.1:8089/v1`; a supplied URL without `/v1`
|
|
26
|
+
has that suffix appended. Production connections should use HTTPS.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from samanapi import Client
|
|
30
|
+
|
|
31
|
+
with Client(base_url="http://127.0.0.1:8089/v1", api_key="YOUR_SERVICE_API_KEY") as client:
|
|
32
|
+
result = client.sign_in() # Username, hidden password, then hidden SMS code if needed
|
|
33
|
+
print(result["bankVerified"])
|
|
34
|
+
print(client.deposits())
|
|
35
|
+
print(client.cards())
|
|
36
|
+
print(client.balance())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The constructor also reads `SAMAN_API_URL` and `SAMAN_API_KEY`. Bank credentials
|
|
40
|
+
are not stored by this Python package. One API server owns one bank session:
|
|
41
|
+
creating another Python client does **not** create an independent bank login.
|
|
42
|
+
`close()` closes HTTP connections; `logout()` clears the shared server session.
|
|
43
|
+
|
|
44
|
+
## Login in a web application
|
|
45
|
+
|
|
46
|
+
Use explicit stages rather than an interactive prompt:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from samanapi import Client
|
|
50
|
+
|
|
51
|
+
with Client() as client:
|
|
52
|
+
result = client.login("YOUR_USERNAME", "YOUR_PASSWORD")
|
|
53
|
+
if result.get("otpRequired"):
|
|
54
|
+
if result.get("otpSent"):
|
|
55
|
+
# Collect the code in your application's UI, then call:
|
|
56
|
+
result = client.verify_otp("123456") # synthetic example code
|
|
57
|
+
else:
|
|
58
|
+
print(result.get("otpError"))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The service requests the device SMS automatically. Do not call `request_otp()`
|
|
62
|
+
after every login: it is an explicit resend. `sign_in()` accepts `otp_callback`
|
|
63
|
+
for integration, and resumes a pending code step when called without credentials.
|
|
64
|
+
Return `resend` from the callback to request another code. Wrong codes leave the
|
|
65
|
+
pending server session available for another attempt. Use `login(..., request_otp=False)`
|
|
66
|
+
only when you deliberately want the service's legacy manual-SMS workflow.
|
|
67
|
+
|
|
68
|
+
`ok=True` can mean that the initial credential step succeeded. A completed login
|
|
69
|
+
has `loggedIn=True` and `bankVerified=True`. Password-change requirements must be
|
|
70
|
+
completed in the bank app. Device-verification, card PIN2 and digital-rial activation
|
|
71
|
+
codes belong to different workflows.
|
|
72
|
+
|
|
73
|
+
## Async client
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from samanapi import AsyncClient
|
|
78
|
+
|
|
79
|
+
async def main():
|
|
80
|
+
async with AsyncClient() as client:
|
|
81
|
+
print(await client.check_session())
|
|
82
|
+
print(await client.latest_transactions("123-456-0000001-2", limit=10))
|
|
83
|
+
|
|
84
|
+
asyncio.run(main())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Every endpoint has the same name and parameters in `Client` and `AsyncClient`.
|
|
88
|
+
`AsyncClient.sign_in` accepts synchronous or asynchronous callbacks. Default
|
|
89
|
+
interactive callbacks run in a worker thread. Transaction iteration uses
|
|
90
|
+
`for row in client.iter_transactions(...)` or `async for` with the async client.
|
|
91
|
+
|
|
92
|
+
## History, details and exact search
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
with Client() as client:
|
|
96
|
+
history = client.transactions(
|
|
97
|
+
"123-456-0000001-2", page=0, size=20,
|
|
98
|
+
details=True, is_withdrawal=False,
|
|
99
|
+
from_date=1700000000, to_date=1700086400,
|
|
100
|
+
)
|
|
101
|
+
result = client.find_transaction(
|
|
102
|
+
"000000000001", "123-456-0000001-2",
|
|
103
|
+
match_by="reference", pages=10, max_transactions=200,
|
|
104
|
+
)
|
|
105
|
+
if not result["found"]:
|
|
106
|
+
print(result["searchComplete"], result["stopReason"])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Pages start at **0**. Transaction dates are Unix **seconds**; merchant summary
|
|
110
|
+
`from_date` is Unix **milliseconds**. Identifiers, tracking/reference numbers,
|
|
111
|
+
cards, deposit numbers and IBANs stay strings, preserving leading zeros.
|
|
112
|
+
Transaction IDs are encoded as one URL segment, including embedded slashes.
|
|
113
|
+
|
|
114
|
+
`details=True` lets the server include full bank detail/receipt objects in each
|
|
115
|
+
row's `detail`. Unknown fields are preserved. The SDK does not infer a sender's
|
|
116
|
+
name or card number from free text. JSON null balances remain `None`, and absent
|
|
117
|
+
fields remain absent; neither is converted to zero. Card and deposit balances
|
|
118
|
+
are not added together.
|
|
119
|
+
|
|
120
|
+
`iter_transactions(..., size=100, max_pages=10, details=False)` is bounded. It raises
|
|
121
|
+
`PaginationError` when a page repeats or the budget runs out before the end of
|
|
122
|
+
history. It does not silently claim that a bounded search covered the whole account.
|
|
123
|
+
|
|
124
|
+
## Transfers and digital rial
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
with Client() as client:
|
|
128
|
+
reasons = client.transfer_reasons()
|
|
129
|
+
owner = client.iban_owner("IR000000000000000000000000") # synthetic, replace it
|
|
130
|
+
# Programmatic money movement is explicit; no confirmation dialog is inserted:
|
|
131
|
+
# receipt = client.transfer_iban(source, destination, amount_irr, reason_code)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Transfer amounts are positive integer **IRR**, capped at JavaScript's safe integer
|
|
135
|
+
range. Card expiry is Jalali `YYYYMM`. `transfer_card` needs `cvv2`, `pin2`, and
|
|
136
|
+
`expire_date`. The server encrypts these fields. PAYA takes a deposit as source
|
|
137
|
+
and an IBAN as destination. Select `reason_code` from `transfer_reasons()`.
|
|
138
|
+
The current service implements CARD, DEPOSIT and PAYA, not SATNA/POL submission.
|
|
139
|
+
|
|
140
|
+
Digital rial has separate eligibility, SMS, activation, balance, transaction,
|
|
141
|
+
charge, discharge, deactivation and contact methods. Wallet history IDs differ
|
|
142
|
+
from deposit-history IDs. Activation currently supports CUSTOMER wallets.
|
|
143
|
+
|
|
144
|
+
The SDK never retries or follows redirects automatically. On a timeout or a
|
|
145
|
+
cancelled payment request, the operation may already have succeeded: inspect
|
|
146
|
+
history before submitting another instruction. API-server renewal behavior is
|
|
147
|
+
separate from the SDK; the companion server never automatically replays transfers.
|
|
148
|
+
|
|
149
|
+
## Errors and response preservation
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from samanapi import Client, ApiError, TransportError
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
with Client() as client:
|
|
156
|
+
print(client.cards())
|
|
157
|
+
except ApiError as exc:
|
|
158
|
+
print(exc.status_code, exc.code, exc.next_action)
|
|
159
|
+
# exc.payload contains the full response; inspect it only where appropriate.
|
|
160
|
+
except TransportError as exc:
|
|
161
|
+
print(exc.request_may_have_succeeded)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Exception types include `AuthenticationError`, `DeviceVerificationRequired`,
|
|
165
|
+
`PasswordChangeRequired`, `ValidationError`, `BankError`, `SmsDeliveryError`,
|
|
166
|
+
`TransportError`, `ProtocolError` and `PaginationError`. HTTP errors preserve
|
|
167
|
+
`payload`, `bank_status`, `auth`, `next_action`, `method` and `path`. Error string
|
|
168
|
+
representations do not print server payloads or credentials. An HTTP 200 body
|
|
169
|
+
containing an application `error` is not silently accepted.
|
|
170
|
+
|
|
171
|
+
The default response limit is 20 MiB; larger responses raise `ProtocolError`
|
|
172
|
+
rather than being truncated. Configure `max_response_bytes` explicitly if needed.
|
|
173
|
+
`timeout` defaults to 120 seconds. Longer bounded searches may need a larger value.
|
|
174
|
+
Custom HTTPX transports are supported for tests and advanced use; their own retry
|
|
175
|
+
or security behavior is the caller's responsibility. `trust_env` defaults to false.
|
|
176
|
+
|
|
177
|
+
## CLI
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
saman-api login
|
|
181
|
+
saman-api status
|
|
182
|
+
saman-api deposits
|
|
183
|
+
saman-api balance
|
|
184
|
+
saman-api transactions --deposit-number 123-456-0000001-2 --page 0 --size 20 --details false
|
|
185
|
+
saman-api find-transaction 000000000001 --deposit-number 123-456-0000001-2 --match-by reference
|
|
186
|
+
saman-api --help
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The API key is read from `SAMAN_API_KEY`. `SAMAN_USER` and `SAMAN_PASS` may supply
|
|
190
|
+
credentials to CLI login. All endpoint methods have hyphenated CLI commands;
|
|
191
|
+
`--help` lists their arguments. Boolean arguments take `true` or `false`.
|
|
192
|
+
Wallet filter lists are comma-separated. Use `python -m samanapi` as an
|
|
193
|
+
alternative entry point. The CLI writes JSON results to stdout and errors to
|
|
194
|
+
stderr; `--json-errors` gives structured errors and exit code 1. Cancellation
|
|
195
|
+
uses exit code 130. Financial commands require `TRANSFER`, wallet activation
|
|
196
|
+
`ACTIVATE`, and deactivation `DEACTIVATE`; noninteractive callers must pass the
|
|
197
|
+
matching `--confirm` value. Required card secrets are prompted without echo
|
|
198
|
+
when omitted. Use `import-har FILE` for a local private capture, never a public example.
|
|
199
|
+
|
|
200
|
+
## API reference and development
|
|
201
|
+
|
|
202
|
+
See [the complete method reference](docs/reference.md) and the
|
|
203
|
+
[HTTP service documentation](https://apibank.heydari.workers.dev/saman/).
|
|
204
|
+
`request("GET", "cards", query={...})` is available for future relative routes;
|
|
205
|
+
absolute/cross-origin paths and traversal are rejected before any I/O.
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
pip install -e .
|
|
209
|
+
python -m unittest discover -s tests -v
|
|
210
|
+
python tools/generate_async.py
|
|
211
|
+
python -m build
|
|
212
|
+
python -m twine check dist/*
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Tests use mock transports and a fake bank behind the real Node server. They do
|
|
216
|
+
not send money or verify a current live bank session. The source distribution
|
|
217
|
+
and wheel use explicit include rules and contain no sessions, captures or API keys.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Release guide
|
|
2
|
+
|
|
3
|
+
Distribution: `saman-api`. Import: `samanapi`. Initial version: `0.1.0`.
|
|
4
|
+
The package requires Python 3.10+ and a running Saman HTTP service 0.3+.
|
|
5
|
+
The Node service itself is not shipped inside the wheel.
|
|
6
|
+
|
|
7
|
+
## Verify locally
|
|
8
|
+
|
|
9
|
+
From the Python package directory:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
python -m pip install -e . build twine pip-audit ruff mypy
|
|
13
|
+
python -m unittest discover -s tests -v
|
|
14
|
+
ruff check src --select E9,F63,F7,F82
|
|
15
|
+
mypy src/samanapi tests/typing_contract.py --check-untyped-defs --warn-unused-ignores
|
|
16
|
+
python tools/generate_async.py
|
|
17
|
+
python -m build
|
|
18
|
+
python -m twine check dist/*
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The build produces a wheel and source archive in `dist/`. Install the wheel in
|
|
22
|
+
a fresh environment and run `saman-api --help`. The integration test requires
|
|
23
|
+
the Node source and its dependencies; install them from the repository root first.
|
|
24
|
+
Standalone SDK distributions skip that repository-only test, while mock transport
|
|
25
|
+
tests remain runnable. Audit the runtime environment with `pip-audit` before release.
|
|
26
|
+
|
|
27
|
+
Keep the version in `pyproject.toml`, `__init__.py` and the client's user-agent
|
|
28
|
+
aligned; regenerate the async client after editing the sync implementation.
|
|
29
|
+
Update CHANGELOG.md and docs/reference.md when changing public methods.
|
|
30
|
+
Use a new version for every PyPI release; PyPI files cannot be overwritten.
|
|
31
|
+
|
|
32
|
+
## Trusted Publishing
|
|
33
|
+
|
|
34
|
+
The repository includes `.github/workflows/publish.yml`, triggered manually from
|
|
35
|
+
the main branch. It tests the server and SDK, builds and checks distributions,
|
|
36
|
+
then publishes with PyPI's short-lived OIDC identity. No PyPI password or token
|
|
37
|
+
is stored in source. It does not run automatically on push.
|
|
38
|
+
|
|
39
|
+
Before the first publication, replace the GitHub owner and repository placeholders
|
|
40
|
+
with the repository you upload this source to. Then sign into your PyPI account, enable its required
|
|
41
|
+
account security and create a pending Trusted Publisher:
|
|
42
|
+
|
|
43
|
+
| Setting | Value |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| PyPI project | `saman-api` |
|
|
46
|
+
| GitHub owner | `DevURANIUM` |
|
|
47
|
+
| Repository | `saman-api` |
|
|
48
|
+
| Workflow filename | `publish.yml` |
|
|
49
|
+
| Environment | `pypi` |
|
|
50
|
+
|
|
51
|
+
Create the `pypi` GitHub environment, restrict deployment to main, and configure
|
|
52
|
+
required reviewers if desired. Then run the publish workflow for the reviewed
|
|
53
|
+
commit. Name availability is not a reservation and must be checked again at release.
|
|
54
|
+
|
|
55
|
+
For a first rehearsal, configure a separate publisher/environment on TestPyPI
|
|
56
|
+
and set the publish action's repository-url to `https://test.pypi.org/legacy/`.
|
|
57
|
+
|
|
58
|
+
See [PyPI's official Trusted Publishing guide](https://docs.pypi.org/trusted-publishers/using-a-publisher/).
|
|
59
|
+
|
|
60
|
+
## Contents and privacy
|
|
61
|
+
|
|
62
|
+
The wheel contains only the SDK, typing marker, package metadata and license.
|
|
63
|
+
The source archive additionally includes tests, examples, documentation and the
|
|
64
|
+
async generator. All examples use synthetic identifiers. Session directories,
|
|
65
|
+
bank captures, environment files, local paths, credentials and private keys must
|
|
66
|
+
never be added to either archive or the GitHub release.
|