makky-acb-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.
- makky_acb_api-0.1.0/MANIFEST.in +1 -0
- makky_acb_api-0.1.0/PKG-INFO +140 -0
- makky_acb_api-0.1.0/README.md +116 -0
- makky_acb_api-0.1.0/pyproject.toml +47 -0
- makky_acb_api-0.1.0/setup.cfg +4 -0
- makky_acb_api-0.1.0/src/acb_api/__init__.py +4 -0
- makky_acb_api-0.1.0/src/acb_api/cli.py +69 -0
- makky_acb_api-0.1.0/src/acb_api/client.py +206 -0
- makky_acb_api-0.1.0/src/acb_api/config.py +16 -0
- makky_acb_api-0.1.0/src/acb_api/exceptions.py +10 -0
- makky_acb_api-0.1.0/src/acb_api/models.py +29 -0
- makky_acb_api-0.1.0/src/makky_acb_api.egg-info/PKG-INFO +140 -0
- makky_acb_api-0.1.0/src/makky_acb_api.egg-info/SOURCES.txt +15 -0
- makky_acb_api-0.1.0/src/makky_acb_api.egg-info/dependency_links.txt +1 -0
- makky_acb_api-0.1.0/src/makky_acb_api.egg-info/entry_points.txt +2 -0
- makky_acb_api-0.1.0/src/makky_acb_api.egg-info/requires.txt +2 -0
- makky_acb_api-0.1.0/src/makky_acb_api.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include README.md
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: makky-acb-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client for ACB account balances and transaction history
|
|
5
|
+
Author: Makky
|
|
6
|
+
Maintainer: Makky
|
|
7
|
+
Project-URL: Homepage, https://github.com/Makky/ACB-API
|
|
8
|
+
Project-URL: Repository, https://github.com/Makky/ACB-API
|
|
9
|
+
Project-URL: Issues, https://github.com/Makky/ACB-API/issues
|
|
10
|
+
Keywords: acb,banking,api,transactions,payments
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: requests>=2.31.0
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
24
|
+
|
|
25
|
+
# makky-acb-api
|
|
26
|
+
|
|
27
|
+
**πΊπΈ English** | [π»π³ TiαΊΏng Viα»t](README_VI.md)
|
|
28
|
+
|
|
29
|
+
Unofficial Python client for ACB account balances and transaction history, packaged for local installation and future PyPI publication.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Authenticate with ACB using username and password
|
|
34
|
+
- Fetch balances for payment accounts
|
|
35
|
+
- Retrieve recent transaction history for a selected account
|
|
36
|
+
- Use as a Python library or from a CLI command
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Local development install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -r requirements.txt
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### After publication to PyPI
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install makky-acb-api
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Environment variables
|
|
54
|
+
|
|
55
|
+
Create a `.env` file in the project root:
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
ACB_USERNAME=your_username
|
|
59
|
+
ACB_PASSWORD=your_password
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> [!WARNING]
|
|
63
|
+
> This project uses an unofficial ACB API. Protect your credentials and use it at your own risk.
|
|
64
|
+
|
|
65
|
+
## Python usage
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from acb_api import ACBClient
|
|
69
|
+
|
|
70
|
+
client = ACBClient(username="your_username", password="your_password")
|
|
71
|
+
balances = client.get_balances()
|
|
72
|
+
print(balances)
|
|
73
|
+
|
|
74
|
+
account = balances["balances"][0]["accountNumber"]
|
|
75
|
+
transactions = client.get_transactions(rows=5, account_number=account)
|
|
76
|
+
print(transactions)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CLI usage
|
|
80
|
+
|
|
81
|
+
After `pip install -e .`, run:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
acb-api --action all --rows 5
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Other examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
acb-api --action balances
|
|
91
|
+
acb-api --action transactions --account 19527581 --rows 10
|
|
92
|
+
acb-api --action all --debug
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Local demo script
|
|
96
|
+
|
|
97
|
+
For backward-compatible local testing:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python main.py
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Project structure
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
ACB-API/
|
|
107
|
+
βββ src/acb_api/
|
|
108
|
+
β βββ __init__.py
|
|
109
|
+
β βββ cli.py
|
|
110
|
+
β βββ client.py
|
|
111
|
+
β βββ config.py
|
|
112
|
+
β βββ exceptions.py
|
|
113
|
+
β βββ models.py
|
|
114
|
+
βββ main.py
|
|
115
|
+
βββ pyproject.toml
|
|
116
|
+
βββ README.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Packaging notes
|
|
120
|
+
|
|
121
|
+
- Distribution/package name: `makky-acb-api`
|
|
122
|
+
- Import package name: `acb_api`
|
|
123
|
+
- Author / maintainer: `Makky`
|
|
124
|
+
|
|
125
|
+
To build distributable artifacts locally:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
python -m build
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Credits
|
|
132
|
+
|
|
133
|
+
This packaged version is maintained by **Makky**.
|
|
134
|
+
|
|
135
|
+
Inspired by earlier community work around ACB unofficial integrations, including:
|
|
136
|
+
- [anhnmt/ACB](https://github.com/anhnmt/ACB)
|
|
137
|
+
|
|
138
|
+
## Disclaimer
|
|
139
|
+
|
|
140
|
+
This is an unofficial client and is not affiliated with ACB. APIs, headers, and authentication flows may change without notice. Use at your own risk.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# makky-acb-api
|
|
2
|
+
|
|
3
|
+
**πΊπΈ English** | [π»π³ TiαΊΏng Viα»t](README_VI.md)
|
|
4
|
+
|
|
5
|
+
Unofficial Python client for ACB account balances and transaction history, packaged for local installation and future PyPI publication.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Authenticate with ACB using username and password
|
|
10
|
+
- Fetch balances for payment accounts
|
|
11
|
+
- Retrieve recent transaction history for a selected account
|
|
12
|
+
- Use as a Python library or from a CLI command
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Local development install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install -r requirements.txt
|
|
20
|
+
pip install -e .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### After publication to PyPI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install makky-acb-api
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Environment variables
|
|
30
|
+
|
|
31
|
+
Create a `.env` file in the project root:
|
|
32
|
+
|
|
33
|
+
```env
|
|
34
|
+
ACB_USERNAME=your_username
|
|
35
|
+
ACB_PASSWORD=your_password
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> [!WARNING]
|
|
39
|
+
> This project uses an unofficial ACB API. Protect your credentials and use it at your own risk.
|
|
40
|
+
|
|
41
|
+
## Python usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from acb_api import ACBClient
|
|
45
|
+
|
|
46
|
+
client = ACBClient(username="your_username", password="your_password")
|
|
47
|
+
balances = client.get_balances()
|
|
48
|
+
print(balances)
|
|
49
|
+
|
|
50
|
+
account = balances["balances"][0]["accountNumber"]
|
|
51
|
+
transactions = client.get_transactions(rows=5, account_number=account)
|
|
52
|
+
print(transactions)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## CLI usage
|
|
56
|
+
|
|
57
|
+
After `pip install -e .`, run:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
acb-api --action all --rows 5
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Other examples:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
acb-api --action balances
|
|
67
|
+
acb-api --action transactions --account 19527581 --rows 10
|
|
68
|
+
acb-api --action all --debug
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Local demo script
|
|
72
|
+
|
|
73
|
+
For backward-compatible local testing:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python main.py
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Project structure
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
ACB-API/
|
|
83
|
+
βββ src/acb_api/
|
|
84
|
+
β βββ __init__.py
|
|
85
|
+
β βββ cli.py
|
|
86
|
+
β βββ client.py
|
|
87
|
+
β βββ config.py
|
|
88
|
+
β βββ exceptions.py
|
|
89
|
+
β βββ models.py
|
|
90
|
+
βββ main.py
|
|
91
|
+
βββ pyproject.toml
|
|
92
|
+
βββ README.md
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Packaging notes
|
|
96
|
+
|
|
97
|
+
- Distribution/package name: `makky-acb-api`
|
|
98
|
+
- Import package name: `acb_api`
|
|
99
|
+
- Author / maintainer: `Makky`
|
|
100
|
+
|
|
101
|
+
To build distributable artifacts locally:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python -m build
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Credits
|
|
108
|
+
|
|
109
|
+
This packaged version is maintained by **Makky**.
|
|
110
|
+
|
|
111
|
+
Inspired by earlier community work around ACB unofficial integrations, including:
|
|
112
|
+
- [anhnmt/ACB](https://github.com/anhnmt/ACB)
|
|
113
|
+
|
|
114
|
+
## Disclaimer
|
|
115
|
+
|
|
116
|
+
This is an unofficial client and is not affiliated with ACB. APIs, headers, and authentication flows may change without notice. Use at your own risk.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "makky-acb-api"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial Python client for ACB account balances and transaction history"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Makky" }
|
|
13
|
+
]
|
|
14
|
+
maintainers = [
|
|
15
|
+
{ name = "Makky" }
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"requests>=2.31.0",
|
|
19
|
+
"python-dotenv>=1.0.0"
|
|
20
|
+
]
|
|
21
|
+
keywords = ["acb", "banking", "api", "transactions", "payments"]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/Makky/ACB-API"
|
|
36
|
+
Repository = "https://github.com/Makky/ACB-API"
|
|
37
|
+
Issues = "https://github.com/Makky/ACB-API/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
acb-api = "acb_api.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools]
|
|
43
|
+
package-dir = {"" = "src"}
|
|
44
|
+
include-package-data = true
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
|
|
10
|
+
from .client import ACBClient
|
|
11
|
+
from .exceptions import ACBError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
15
|
+
parser = argparse.ArgumentParser(description="Unofficial ACB API CLI")
|
|
16
|
+
parser.add_argument("--username", help="ACB username")
|
|
17
|
+
parser.add_argument("--password", help="ACB password")
|
|
18
|
+
parser.add_argument("--rows", type=int, default=5, help="Number of transactions to fetch")
|
|
19
|
+
parser.add_argument("--account", help="Account number to fetch transactions for")
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--action",
|
|
22
|
+
choices=["balances", "transactions", "all"],
|
|
23
|
+
default="all",
|
|
24
|
+
help="Operation to perform",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("--debug", action="store_true", help="Enable verbose HTTP debug output")
|
|
27
|
+
return parser
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_credentials(username: Optional[str], password: Optional[str]) -> tuple[str, str]:
|
|
31
|
+
load_dotenv()
|
|
32
|
+
final_username = username or os.getenv("ACB_USERNAME")
|
|
33
|
+
final_password = password or os.getenv("ACB_PASSWORD")
|
|
34
|
+
|
|
35
|
+
if not final_username or not final_password:
|
|
36
|
+
raise ValueError("Missing credentials. Provide --username/--password or set ACB_USERNAME and ACB_PASSWORD.")
|
|
37
|
+
|
|
38
|
+
return final_username, final_password
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> int:
|
|
42
|
+
parser = build_parser()
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
username, password = resolve_credentials(args.username, args.password)
|
|
47
|
+
client = ACBClient(username=username, password=password, debug=args.debug)
|
|
48
|
+
|
|
49
|
+
if args.action in {"balances", "all"}:
|
|
50
|
+
balances = client.get_balances()
|
|
51
|
+
print(json.dumps({"balances": balances["balances"]}, indent=2, ensure_ascii=False))
|
|
52
|
+
|
|
53
|
+
if args.action in {"transactions", "all"}:
|
|
54
|
+
account = args.account or client.get_first_account_number()
|
|
55
|
+
if not account:
|
|
56
|
+
print("No account available for transaction lookup.")
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
transactions = client.get_transactions(rows=args.rows, account_number=account)
|
|
60
|
+
print(json.dumps({"account": account, "transactions": transactions}, indent=2, ensure_ascii=False))
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
except (ValueError, ACBError) as exc:
|
|
64
|
+
print(f"Error: {exc}")
|
|
65
|
+
return 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from .config import (
|
|
10
|
+
DEFAULT_BASE_URL,
|
|
11
|
+
DEFAULT_CLIENT_ID,
|
|
12
|
+
DEFAULT_SESSION_HEADERS,
|
|
13
|
+
DEFAULT_TIMEOUT,
|
|
14
|
+
)
|
|
15
|
+
from .exceptions import ACBAuthenticationError, ACBRequestError
|
|
16
|
+
from .models import AccountBalance, TransactionRecord
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _generate_uuid() -> str:
|
|
20
|
+
return str(uuid.uuid4())
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _generate_device_id() -> str:
|
|
24
|
+
return str(uuid.uuid4()).upper()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ACBClient:
|
|
28
|
+
"""Unofficial ACB API client for balances and transaction history."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
username: str,
|
|
33
|
+
password: str,
|
|
34
|
+
*,
|
|
35
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
36
|
+
client_id: str = DEFAULT_CLIENT_ID,
|
|
37
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
38
|
+
debug: bool = False,
|
|
39
|
+
session: Optional[requests.Session] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
if not username or not password:
|
|
42
|
+
raise ValueError("Username and password cannot be empty.")
|
|
43
|
+
|
|
44
|
+
self.username = username.strip()
|
|
45
|
+
self.password = password.strip()
|
|
46
|
+
self.base_url = base_url.rstrip("/")
|
|
47
|
+
self.client_id = client_id
|
|
48
|
+
self.timeout = timeout
|
|
49
|
+
self.debug = debug
|
|
50
|
+
self.device_id = _generate_device_id()
|
|
51
|
+
self.access_token: Optional[str] = None
|
|
52
|
+
self.refresh_token: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
self.session = session or requests.Session()
|
|
55
|
+
self.session.headers.update(DEFAULT_SESSION_HEADERS)
|
|
56
|
+
|
|
57
|
+
def _debug(self, message: str) -> None:
|
|
58
|
+
if self.debug:
|
|
59
|
+
print(message)
|
|
60
|
+
|
|
61
|
+
def _get_request_headers(self, include_auth: bool = False) -> Dict[str, str]:
|
|
62
|
+
headers = {"x-request-id": _generate_uuid()}
|
|
63
|
+
if include_auth and self.access_token:
|
|
64
|
+
headers["Authorization"] = f"Bearer {self.access_token}"
|
|
65
|
+
return headers
|
|
66
|
+
|
|
67
|
+
def _request(
|
|
68
|
+
self,
|
|
69
|
+
method: str,
|
|
70
|
+
path: str,
|
|
71
|
+
*,
|
|
72
|
+
headers: Optional[Dict[str, str]] = None,
|
|
73
|
+
data: Optional[str] = None,
|
|
74
|
+
auth_required: bool = False,
|
|
75
|
+
) -> requests.Response:
|
|
76
|
+
request_headers = self._get_request_headers(include_auth=auth_required)
|
|
77
|
+
if headers:
|
|
78
|
+
request_headers.update(headers)
|
|
79
|
+
|
|
80
|
+
url = f"{self.base_url}{path}"
|
|
81
|
+
self._debug(f"{method.upper()} {url}")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
response = self.session.request(
|
|
85
|
+
method=method.upper(),
|
|
86
|
+
url=url,
|
|
87
|
+
headers=request_headers,
|
|
88
|
+
data=data,
|
|
89
|
+
timeout=self.timeout,
|
|
90
|
+
)
|
|
91
|
+
except requests.exceptions.RequestException as exc:
|
|
92
|
+
raise ACBRequestError(f"Request to ACB failed: {exc}") from exc
|
|
93
|
+
|
|
94
|
+
self._debug(f"Status: {response.status_code}")
|
|
95
|
+
if self.debug:
|
|
96
|
+
self._debug(f"Response body: {response.text}")
|
|
97
|
+
|
|
98
|
+
return response
|
|
99
|
+
|
|
100
|
+
def login(self) -> None:
|
|
101
|
+
payload = {
|
|
102
|
+
"username": self.username,
|
|
103
|
+
"password": self.password,
|
|
104
|
+
"deviceId": self.device_id,
|
|
105
|
+
"clientId": self.client_id,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
response = self._request(
|
|
109
|
+
"POST",
|
|
110
|
+
"/mb/v2/auth/tokens",
|
|
111
|
+
data=json.dumps(payload),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if not response.ok:
|
|
115
|
+
raise ACBAuthenticationError(
|
|
116
|
+
f"Authentication failed ({response.status_code}): {response.text}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
login_data = response.json()
|
|
120
|
+
self.access_token = login_data.get("accessToken")
|
|
121
|
+
self.refresh_token = login_data.get("refreshToken")
|
|
122
|
+
|
|
123
|
+
if not self.access_token:
|
|
124
|
+
raise ACBAuthenticationError("Authentication succeeded but no access token was returned.")
|
|
125
|
+
|
|
126
|
+
def refresh(self) -> None:
|
|
127
|
+
if not self.refresh_token:
|
|
128
|
+
self.login()
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
response = self._request(
|
|
132
|
+
"POST",
|
|
133
|
+
"/mb/v2/auth/refresh",
|
|
134
|
+
headers={"Authorization": f"Bearer {self.refresh_token}"},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if not response.ok:
|
|
138
|
+
self._debug("Refresh failed, retrying with full login.")
|
|
139
|
+
self.login()
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
refresh_data = response.json()
|
|
143
|
+
self.access_token = refresh_data.get("accessToken")
|
|
144
|
+
if not self.access_token:
|
|
145
|
+
self.login()
|
|
146
|
+
|
|
147
|
+
def authenticate(self) -> None:
|
|
148
|
+
if self.refresh_token:
|
|
149
|
+
self.refresh()
|
|
150
|
+
else:
|
|
151
|
+
self.login()
|
|
152
|
+
|
|
153
|
+
def get_balances(self) -> Dict[str, List[AccountBalance]]:
|
|
154
|
+
self.authenticate()
|
|
155
|
+
response = self._request(
|
|
156
|
+
"GET",
|
|
157
|
+
"/mb/legacy/ss/cs/bankservice/transfers/list/account-payment",
|
|
158
|
+
auth_required=True,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if not response.ok:
|
|
162
|
+
raise ACBRequestError(
|
|
163
|
+
f"Failed to fetch balances ({response.status_code}): {response.text}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
payload = response.json()
|
|
167
|
+
balances: List[AccountBalance] = []
|
|
168
|
+
for account in payload.get("data", []):
|
|
169
|
+
balances.append(
|
|
170
|
+
AccountBalance(
|
|
171
|
+
accountNumber=str(account.get("accountNumber", "")),
|
|
172
|
+
accountDescription=account.get("accountDescription", ""),
|
|
173
|
+
ownerName=account.get("owner", ""),
|
|
174
|
+
currency=account.get("currency", ""),
|
|
175
|
+
balance=float(account.get("balance", 0) or 0),
|
|
176
|
+
totalBalance=float(account.get("totalBalance", 0) or 0),
|
|
177
|
+
status=int(account.get("status", 0) or 0),
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
return {"balances": balances}
|
|
181
|
+
|
|
182
|
+
def get_transactions(self, rows: int, account_number: str) -> List[TransactionRecord]:
|
|
183
|
+
self.authenticate()
|
|
184
|
+
path = (
|
|
185
|
+
f"/mb/legacy/ss/cs/bankservice/saving/tx-history?maxRows={rows}"
|
|
186
|
+
f"&account={account_number}"
|
|
187
|
+
)
|
|
188
|
+
response = self._request("GET", path, auth_required=True)
|
|
189
|
+
|
|
190
|
+
if not response.ok:
|
|
191
|
+
raise ACBRequestError(
|
|
192
|
+
f"Failed to fetch transactions ({response.status_code}): {response.text}"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
payload = response.json()
|
|
196
|
+
data = payload.get("data", [])
|
|
197
|
+
return [TransactionRecord(**item) for item in data]
|
|
198
|
+
|
|
199
|
+
def get_first_account_number(self) -> Optional[str]:
|
|
200
|
+
balances = self.get_balances().get("balances", [])
|
|
201
|
+
if not balances:
|
|
202
|
+
return None
|
|
203
|
+
return balances[0]["accountNumber"]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
ACB = ACBClient
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
DEFAULT_BASE_URL = "https://apiapp.acb.com.vn"
|
|
2
|
+
DEFAULT_CLIENT_ID = "iuSuHYVufIUuNIREV0FB9EoLn9kHsDbm"
|
|
3
|
+
DEFAULT_API_KEY = "CQk6S5usauGmMgMYLGqCuDtgtqIM8FI1"
|
|
4
|
+
DEFAULT_APP_VERSION = "3.26.0"
|
|
5
|
+
DEFAULT_USER_AGENT = "ACB-MBA/8 CFNetwork/1335.0.3 Darwin/21.6.0"
|
|
6
|
+
DEFAULT_CONVERSATION_ID = "f43472f1-6d88-4228-91b9-4618a079342a"
|
|
7
|
+
DEFAULT_TIMEOUT = 30
|
|
8
|
+
|
|
9
|
+
DEFAULT_SESSION_HEADERS = {
|
|
10
|
+
"Accept": "application/json, text/plain, */*",
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
"apikey": DEFAULT_API_KEY,
|
|
13
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
14
|
+
"x-app-version": DEFAULT_APP_VERSION,
|
|
15
|
+
"x-conversation-id": DEFAULT_CONVERSATION_ID,
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AccountBalance(TypedDict):
|
|
5
|
+
accountNumber: str
|
|
6
|
+
accountDescription: str
|
|
7
|
+
ownerName: str
|
|
8
|
+
currency: str
|
|
9
|
+
balance: float
|
|
10
|
+
totalBalance: float
|
|
11
|
+
status: int
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TransactionRecord(TypedDict, total=False):
|
|
15
|
+
amount: float
|
|
16
|
+
accountName: str
|
|
17
|
+
receiverName: str
|
|
18
|
+
transactionNumber: int
|
|
19
|
+
description: str
|
|
20
|
+
bankName: str
|
|
21
|
+
isOnline: bool
|
|
22
|
+
postingDate: int
|
|
23
|
+
accountOwner: str
|
|
24
|
+
type: str
|
|
25
|
+
receiverAccountNumber: str
|
|
26
|
+
currency: str
|
|
27
|
+
account: int
|
|
28
|
+
activeDatetime: int
|
|
29
|
+
effectiveDate: int
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: makky-acb-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client for ACB account balances and transaction history
|
|
5
|
+
Author: Makky
|
|
6
|
+
Maintainer: Makky
|
|
7
|
+
Project-URL: Homepage, https://github.com/Makky/ACB-API
|
|
8
|
+
Project-URL: Repository, https://github.com/Makky/ACB-API
|
|
9
|
+
Project-URL: Issues, https://github.com/Makky/ACB-API/issues
|
|
10
|
+
Keywords: acb,banking,api,transactions,payments
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: requests>=2.31.0
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
24
|
+
|
|
25
|
+
# makky-acb-api
|
|
26
|
+
|
|
27
|
+
**πΊπΈ English** | [π»π³ TiαΊΏng Viα»t](README_VI.md)
|
|
28
|
+
|
|
29
|
+
Unofficial Python client for ACB account balances and transaction history, packaged for local installation and future PyPI publication.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Authenticate with ACB using username and password
|
|
34
|
+
- Fetch balances for payment accounts
|
|
35
|
+
- Retrieve recent transaction history for a selected account
|
|
36
|
+
- Use as a Python library or from a CLI command
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Local development install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -r requirements.txt
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### After publication to PyPI
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install makky-acb-api
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Environment variables
|
|
54
|
+
|
|
55
|
+
Create a `.env` file in the project root:
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
ACB_USERNAME=your_username
|
|
59
|
+
ACB_PASSWORD=your_password
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> [!WARNING]
|
|
63
|
+
> This project uses an unofficial ACB API. Protect your credentials and use it at your own risk.
|
|
64
|
+
|
|
65
|
+
## Python usage
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from acb_api import ACBClient
|
|
69
|
+
|
|
70
|
+
client = ACBClient(username="your_username", password="your_password")
|
|
71
|
+
balances = client.get_balances()
|
|
72
|
+
print(balances)
|
|
73
|
+
|
|
74
|
+
account = balances["balances"][0]["accountNumber"]
|
|
75
|
+
transactions = client.get_transactions(rows=5, account_number=account)
|
|
76
|
+
print(transactions)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CLI usage
|
|
80
|
+
|
|
81
|
+
After `pip install -e .`, run:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
acb-api --action all --rows 5
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Other examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
acb-api --action balances
|
|
91
|
+
acb-api --action transactions --account 19527581 --rows 10
|
|
92
|
+
acb-api --action all --debug
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Local demo script
|
|
96
|
+
|
|
97
|
+
For backward-compatible local testing:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python main.py
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Project structure
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
ACB-API/
|
|
107
|
+
βββ src/acb_api/
|
|
108
|
+
β βββ __init__.py
|
|
109
|
+
β βββ cli.py
|
|
110
|
+
β βββ client.py
|
|
111
|
+
β βββ config.py
|
|
112
|
+
β βββ exceptions.py
|
|
113
|
+
β βββ models.py
|
|
114
|
+
βββ main.py
|
|
115
|
+
βββ pyproject.toml
|
|
116
|
+
βββ README.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Packaging notes
|
|
120
|
+
|
|
121
|
+
- Distribution/package name: `makky-acb-api`
|
|
122
|
+
- Import package name: `acb_api`
|
|
123
|
+
- Author / maintainer: `Makky`
|
|
124
|
+
|
|
125
|
+
To build distributable artifacts locally:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
python -m build
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Credits
|
|
132
|
+
|
|
133
|
+
This packaged version is maintained by **Makky**.
|
|
134
|
+
|
|
135
|
+
Inspired by earlier community work around ACB unofficial integrations, including:
|
|
136
|
+
- [anhnmt/ACB](https://github.com/anhnmt/ACB)
|
|
137
|
+
|
|
138
|
+
## Disclaimer
|
|
139
|
+
|
|
140
|
+
This is an unofficial client and is not affiliated with ACB. APIs, headers, and authentication flows may change without notice. Use at your own risk.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/acb_api/__init__.py
|
|
5
|
+
src/acb_api/cli.py
|
|
6
|
+
src/acb_api/client.py
|
|
7
|
+
src/acb_api/config.py
|
|
8
|
+
src/acb_api/exceptions.py
|
|
9
|
+
src/acb_api/models.py
|
|
10
|
+
src/makky_acb_api.egg-info/PKG-INFO
|
|
11
|
+
src/makky_acb_api.egg-info/SOURCES.txt
|
|
12
|
+
src/makky_acb_api.egg-info/dependency_links.txt
|
|
13
|
+
src/makky_acb_api.egg-info/entry_points.txt
|
|
14
|
+
src/makky_acb_api.egg-info/requires.txt
|
|
15
|
+
src/makky_acb_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
acb_api
|