vnqr 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.
- vnqr-0.1.0/PKG-INFO +421 -0
- vnqr-0.1.0/README.md +400 -0
- vnqr-0.1.0/pyproject.toml +81 -0
- vnqr-0.1.0/src/vnqr/__init__.py +11 -0
- vnqr-0.1.0/src/vnqr/constants.py +60 -0
- vnqr-0.1.0/src/vnqr/crc.py +14 -0
- vnqr-0.1.0/src/vnqr/element.py +60 -0
- vnqr-0.1.0/src/vnqr/qr.py +147 -0
- vnqr-0.1.0/src/vnqr/tools/__init__.py +5 -0
- vnqr-0.1.0/src/vnqr/tools/craw_bank.py +112 -0
- vnqr-0.1.0/src/vnqr/vietqr.py +93 -0
vnqr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: vnqr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Thư viện Python để tạo mã QR thanh toán VietQR theo tiêu chuẩn NAPAS, hỗ trợ tạo hình ảnh QR code và thêm icon tùy chỉnh
|
|
5
|
+
Keywords: vietqr,qr-code,bank,vietnam,Viet Nam,qr
|
|
6
|
+
Author: Do Quoc Vuong
|
|
7
|
+
Author-email: Do Quoc Vuong <vuongtlt13@gmail.com>
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: crcmod>=1.7
|
|
12
|
+
Requires-Dist: pypng>=0.20220715.0
|
|
13
|
+
Requires-Dist: qrcode>=8.2
|
|
14
|
+
Requires-Dist: requests>=2.32.5
|
|
15
|
+
Requires-Dist: pillow>=10.0.0
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Project-URL: Homepage, https://github.com/vuongtlt13/vietqr
|
|
18
|
+
Project-URL: Issues, https://github.com/vuongtlt13/vietqr/issues
|
|
19
|
+
Project-URL: License, https://opensource.org/licenses/MIT
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# vnqr 🇻🇳
|
|
23
|
+
|
|
24
|
+
Thư viện Python để tạo VietQR code - mã QR thanh toán chuẩn Việt Nam theo tiêu chuẩn NAPAS.
|
|
25
|
+
|
|
26
|
+
## ✨ Tính năng
|
|
27
|
+
|
|
28
|
+
- ✅ Tạo chuỗi VietQR theo tiêu chuẩn NAPAS
|
|
29
|
+
- ✅ Tạo hình ảnh QR code từ chuỗi VietQR
|
|
30
|
+
- ✅ Hỗ trợ các ngân hàng tại Việt Nam
|
|
31
|
+
- ✅ Type hints đầy đủ
|
|
32
|
+
|
|
33
|
+
## 📦 Cài đặt
|
|
34
|
+
|
|
35
|
+
### Cài đặt cơ bản
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install vnqr
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Hoặc sử dụng `uv`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv pip install vnqr
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🚀 Sử dụng nhanh
|
|
48
|
+
|
|
49
|
+
### Tạo chuỗi VietQR
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from vnqr import generate_vietqr_string
|
|
53
|
+
|
|
54
|
+
vietqr_string = generate_vietqr_string(
|
|
55
|
+
bank_bin="970425", # Mã BIN của ngân hàng
|
|
56
|
+
bank_account="123456789", # Số tài khoản
|
|
57
|
+
total_amount=100000, # Số tiền (VNĐ)
|
|
58
|
+
content="Thanh toan don hang" # Nội dung giao dịch
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
print(vietqr_string)
|
|
62
|
+
# Output: 00020101021238530010A0000007270123000697042501091234567890208QRIBFTTA5303704540710000005802VN62150811Thanh toan don hang6304...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Tạo hình ảnh QR code
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
69
|
+
import base64
|
|
70
|
+
|
|
71
|
+
# Tạo chuỗi VietQR
|
|
72
|
+
vietqr_string = generate_vietqr_string(
|
|
73
|
+
bank_bin="970425",
|
|
74
|
+
bank_account="123456789",
|
|
75
|
+
total_amount=100000,
|
|
76
|
+
content="Thanh toan don hang"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Tạo hình ảnh QR code (dạng base64)
|
|
80
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
81
|
+
|
|
82
|
+
# Lưu vào file
|
|
83
|
+
image_data = base64.b64decode(qr_image_base64)
|
|
84
|
+
with open("vietqr.png", "wb") as f:
|
|
85
|
+
f.write(image_data)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Tạo QR code với icon
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import io
|
|
92
|
+
from vnqr import generate_vietqr_string, generate_qr_image_with_icon
|
|
93
|
+
|
|
94
|
+
# Tạo chuỗi VietQR
|
|
95
|
+
qr_string = generate_vietqr_string(
|
|
96
|
+
bank_bin="970425",
|
|
97
|
+
bank_account="123456789",
|
|
98
|
+
total_amount=100000,
|
|
99
|
+
content="Thanh toan don hang",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Cách 1: truyền đường dẫn file icon
|
|
103
|
+
qr_with_logo = generate_qr_image_with_icon(
|
|
104
|
+
qr_string=qr_string,
|
|
105
|
+
icon_path="examples/logo.png",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Cách 2: đọc icon thành bytes rồi wrap bằng BytesIO
|
|
109
|
+
with open("examples/logo.png", "rb") as f:
|
|
110
|
+
icon_bytes = f.read()
|
|
111
|
+
|
|
112
|
+
qr_with_logo_from_bytes = generate_qr_image_with_icon(
|
|
113
|
+
qr_string=qr_string,
|
|
114
|
+
icon_path=io.BytesIO(icon_bytes), # BinaryIO được hỗ trợ
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 📚 API Reference
|
|
119
|
+
|
|
120
|
+
### `generate_vietqr_string()`
|
|
121
|
+
|
|
122
|
+
Tạo chuỗi VietQR theo tiêu chuẩn NAPAS.
|
|
123
|
+
|
|
124
|
+
**Tham số:**
|
|
125
|
+
- `bank_bin` (str): Mã BIN của ngân hàng (6 chữ số). Ví dụ: "970425" (Techcombank)
|
|
126
|
+
- `bank_account` (str): Số tài khoản ngân hàng
|
|
127
|
+
- `total_amount` (int): Số tiền giao dịch (VNĐ)
|
|
128
|
+
- `content` (str): Nội dung giao dịch
|
|
129
|
+
|
|
130
|
+
**Trả về:**
|
|
131
|
+
- `str`: Chuỗi VietQR
|
|
132
|
+
|
|
133
|
+
**Ví dụ:**
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from vnqr import generate_vietqr_string
|
|
137
|
+
|
|
138
|
+
vietqr_string = generate_vietqr_string(
|
|
139
|
+
bank_bin="970425",
|
|
140
|
+
bank_account="123456789",
|
|
141
|
+
total_amount=100000,
|
|
142
|
+
content="Thanh toan don hang"
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `generate_qr_image()`
|
|
147
|
+
|
|
148
|
+
Tạo hình ảnh QR code từ chuỗi VietQR.
|
|
149
|
+
|
|
150
|
+
**Tham số:**
|
|
151
|
+
- `qr_string` (str): Chuỗi VietQR
|
|
152
|
+
|
|
153
|
+
**Trả về:**
|
|
154
|
+
- `str`: Hình ảnh QR code dạng base64
|
|
155
|
+
|
|
156
|
+
**Ví dụ:**
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from vnqr import generate_qr_image
|
|
160
|
+
|
|
161
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### `generate_qr_image_with_icon()`
|
|
165
|
+
|
|
166
|
+
Tạo hình ảnh QR code với icon đặt giữa (ví dụ logo thương hiệu).
|
|
167
|
+
|
|
168
|
+
**Tham số:**
|
|
169
|
+
- `qr_string` (str): Chuỗi VietQR
|
|
170
|
+
- `icon_path` (str | Path | BinaryIO): Đường dẫn tới icon hoặc đối tượng file-like (có `.read()` trả về bytes). Có thể đọc file ảnh vào bytes rồi truyền `io.BytesIO(icon_bytes)`.
|
|
171
|
+
- `icon_size_ratio` (float): Tỷ lệ kích thước icon so với QR (mặc định 0.25)
|
|
172
|
+
- `error_correction` (int): Mức sửa lỗi QR, mặc định `qrcode.ERROR_CORRECT_H`
|
|
173
|
+
|
|
174
|
+
**Trả về:**
|
|
175
|
+
- `str`: Hình ảnh QR code dạng base64
|
|
176
|
+
|
|
177
|
+
**Ví dụ:**
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
import io
|
|
181
|
+
from vnqr import generate_qr_image_with_icon
|
|
182
|
+
|
|
183
|
+
with open("logo.png", "rb") as f:
|
|
184
|
+
icon_bytes = f.read()
|
|
185
|
+
|
|
186
|
+
qr_image_base64 = generate_qr_image_with_icon(
|
|
187
|
+
qr_string=vietqr_string,
|
|
188
|
+
icon_path=io.BytesIO(icon_bytes),
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 💡 Examples / Ví dụ sử dụng
|
|
193
|
+
|
|
194
|
+
### Example 1: Sử dụng đơn giản nhất
|
|
195
|
+
|
|
196
|
+
Xem file [`examples/simple_example.py`](examples/simple_example.py):
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
200
|
+
|
|
201
|
+
qr_string = generate_vietqr_string(
|
|
202
|
+
bank_bin="970425",
|
|
203
|
+
bank_account="123456789",
|
|
204
|
+
total_amount=100000,
|
|
205
|
+
content="Thanh toan don hang"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
qr_image = generate_qr_image(qr_string)
|
|
209
|
+
print("VietQR String:", qr_string)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Chạy ví dụ:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
python examples/simple_example.py
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Example 2: Sử dụng cơ bản với HTML (xem trực quan QR)
|
|
219
|
+
|
|
220
|
+
Xem file [`examples/basic_usage.py`](examples/basic_usage.py):
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
224
|
+
|
|
225
|
+
# Tạo chuỗi VietQR
|
|
226
|
+
vietqr_string = generate_vietqr_string(
|
|
227
|
+
bank_bin="970425",
|
|
228
|
+
bank_account="123456789",
|
|
229
|
+
total_amount=100000,
|
|
230
|
+
content="Thanh toan don hang"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Tạo QR code image
|
|
234
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
235
|
+
|
|
236
|
+
# Lưu vào file HTML để xem
|
|
237
|
+
# ... (xem file example để biết chi tiết)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Chạy ví dụ:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
python examples/basic_usage.py
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Kết quả:
|
|
247
|
+
|
|
248
|
+
- File `vietqr_example.html` được tạo trong thư mục `examples/`
|
|
249
|
+
- Mở file này bằng trình duyệt, bạn sẽ thấy:
|
|
250
|
+
- **Ảnh QR code** có thể quét bằng app ngân hàng
|
|
251
|
+
- **Thông tin giao dịch**: BIN, số tài khoản, số tiền, nội dung
|
|
252
|
+
- **Chuỗi VietQR** đầy đủ hiển thị bên dưới ảnh
|
|
253
|
+
|
|
254
|
+
### Example 3: Lưu QR code vào file
|
|
255
|
+
|
|
256
|
+
Xem file [`examples/save_qr_image.py`](examples/save_qr_image.py):
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
import base64
|
|
260
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
261
|
+
|
|
262
|
+
vietqr_string = generate_vietqr_string(
|
|
263
|
+
bank_bin="970422",
|
|
264
|
+
bank_account="987654321",
|
|
265
|
+
total_amount=500000,
|
|
266
|
+
content="Noi dung chuyen khoan"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
270
|
+
|
|
271
|
+
# Lưu vào file PNG
|
|
272
|
+
image_data = base64.b64decode(qr_image_base64)
|
|
273
|
+
with open("vietqr_code.png", "wb") as f:
|
|
274
|
+
f.write(image_data)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Chạy ví dụ:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
python examples/save_qr_image.py
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Kết quả:
|
|
284
|
+
|
|
285
|
+
- File ảnh `vietqr_code.png` được tạo trong thư mục `examples/`
|
|
286
|
+
- Đây là QR code hoàn chỉnh, có thể mở trực tiếp bằng trình xem ảnh hoặc quét bằng ứng dụng ngân hàng
|
|
287
|
+
|
|
288
|
+
📸 **Ảnh minh họa QR code tạo từ ví dụ:**
|
|
289
|
+
|
|
290
|
+

|
|
291
|
+
|
|
292
|
+
### Example 4: Crawl danh sách ngân hàng từ API VietQR
|
|
293
|
+
|
|
294
|
+
Xem file [`examples/crawl_banks.py`](examples/crawl_banks.py):
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from vnqr.tools import crawl_bank_info
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def main():
|
|
301
|
+
crawl_bank_info(output_file="banks.json")
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Chạy ví dụ:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
python examples/crawl_banks.py
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Kết quả:
|
|
311
|
+
|
|
312
|
+
- Gọi API `https://api.vietqr.io/v2/banks` để lấy danh sách ngân hàng mới nhất
|
|
313
|
+
- Lưu dữ liệu vào file `banks.json` (bao gồm mã BIN, tên ngân hàng, trạng thái hỗ trợ chuyển khoản…)
|
|
314
|
+
- In thống kê nhanh: tổng số ngân hàng, bao nhiêu ngân hàng hỗ trợ chuyển khoản, liệt kê vài ngân hàng đầu tiên
|
|
315
|
+
|
|
316
|
+
Bạn cũng có thể chạy trực tiếp module tiện ích:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
python -m vnqr.tools.craw_bank
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## 🏦 Mã BIN của các ngân hàng phổ biến
|
|
323
|
+
|
|
324
|
+
Dưới đây là một số mã BIN phổ biến (6 chữ số):
|
|
325
|
+
|
|
326
|
+
| Ngân hàng | Mã BIN |
|
|
327
|
+
|-----------|--------|
|
|
328
|
+
| Techcombank | 970425 |
|
|
329
|
+
| Vietinbank | 970422 |
|
|
330
|
+
| Vietcombank | 970436 |
|
|
331
|
+
| BIDV | 970418 |
|
|
332
|
+
| Agribank | 970405 |
|
|
333
|
+
| ACB | 970416 |
|
|
334
|
+
| VPBank | 970432 |
|
|
335
|
+
| TPBank | 970423 |
|
|
336
|
+
| MBBank | 970422 |
|
|
337
|
+
|
|
338
|
+
> **Lưu ý:** Mã BIN có thể khác nhau tùy theo loại thẻ. Vui lòng kiểm tra với ngân hàng của bạn để có mã BIN chính xác.
|
|
339
|
+
|
|
340
|
+
## 🔧 Yêu cầu hệ thống
|
|
341
|
+
|
|
342
|
+
- Python >= 3.10
|
|
343
|
+
- Dependencies:
|
|
344
|
+
- `crcmod >= 1.7` - Tính toán CRC
|
|
345
|
+
- `pypng >= 0.20220715.0` - Xử lý hình ảnh PNG
|
|
346
|
+
- `qrcode >= 8.2` - Tạo QR code
|
|
347
|
+
|
|
348
|
+
## 📝 Cấu trúc dự án
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
vnqr/
|
|
352
|
+
├── src/
|
|
353
|
+
│ └── vnqr/
|
|
354
|
+
│ ├── __init__.py
|
|
355
|
+
│ ├── constants.py # Các hằng số VietQR
|
|
356
|
+
│ ├── crc.py # Tính toán CRC
|
|
357
|
+
│ ├── element.py # Class Element
|
|
358
|
+
│ ├── qr.py # Tạo QR code image
|
|
359
|
+
│ └── vietqr.py # Tạo chuỗi VietQR
|
|
360
|
+
├── examples/ # Các ví dụ sử dụng
|
|
361
|
+
├── tests/ # Unit tests
|
|
362
|
+
├── pyproject.toml
|
|
363
|
+
└── README.md
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## 🧪 Testing
|
|
367
|
+
|
|
368
|
+
Chạy tests:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
pytest
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Với coverage:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
pytest --cov=vnqr
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## 🤝 Đóng góp
|
|
381
|
+
|
|
382
|
+
Mọi đóng góp đều được chào đón! Vui lòng:
|
|
383
|
+
|
|
384
|
+
1. Fork dự án
|
|
385
|
+
2. Tạo branch cho feature mới (`git checkout -b feature/AmazingFeature`)
|
|
386
|
+
3. Commit các thay đổi (`git commit -m 'Add some AmazingFeature'`)
|
|
387
|
+
4. Push lên branch (`git push origin feature/AmazingFeature`)
|
|
388
|
+
5. Mở Pull Request
|
|
389
|
+
|
|
390
|
+
## 📄 License
|
|
391
|
+
|
|
392
|
+
Dự án này được phân phối dưới giấy phép MIT. Xem file [LICENSE](LICENSE) để biết thêm chi tiết.
|
|
393
|
+
|
|
394
|
+
## 👤 Author
|
|
395
|
+
|
|
396
|
+
**Do Quoc Vuong**
|
|
397
|
+
- Email: vuongtlt13@gmail.com
|
|
398
|
+
- GitHub: [@vuongtlt13](https://github.com/vuongtlt13)
|
|
399
|
+
|
|
400
|
+
## 🔗 Links
|
|
401
|
+
|
|
402
|
+
- [Homepage](https://github.com/vuongtlt13/vietqr)
|
|
403
|
+
- [Issues](https://github.com/vuongtlt13/vietqr/issues)
|
|
404
|
+
- [License](https://opensource.org/licenses/MIT)
|
|
405
|
+
|
|
406
|
+
## ⚠️ Lưu ý
|
|
407
|
+
|
|
408
|
+
- Package này tạo VietQR code theo tiêu chuẩn NAPAS
|
|
409
|
+
- Mã BIN cần phải chính xác để QR code hoạt động đúng
|
|
410
|
+
- Đảm bảo số tiền và thông tin giao dịch là hợp lệ
|
|
411
|
+
- QR code được tạo là QR động (one-time use)
|
|
412
|
+
|
|
413
|
+
## 🙏 Acknowledgments
|
|
414
|
+
|
|
415
|
+
- NAPAS - Tiêu chuẩn VietQR
|
|
416
|
+
- Thư viện [qrcode](https://github.com/lincolnloop/python-qrcode) - Tạo QR code
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
Made with ❤️ in Vietnam
|
|
421
|
+
|
vnqr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# vnqr 🇻🇳
|
|
2
|
+
|
|
3
|
+
Thư viện Python để tạo VietQR code - mã QR thanh toán chuẩn Việt Nam theo tiêu chuẩn NAPAS.
|
|
4
|
+
|
|
5
|
+
## ✨ Tính năng
|
|
6
|
+
|
|
7
|
+
- ✅ Tạo chuỗi VietQR theo tiêu chuẩn NAPAS
|
|
8
|
+
- ✅ Tạo hình ảnh QR code từ chuỗi VietQR
|
|
9
|
+
- ✅ Hỗ trợ các ngân hàng tại Việt Nam
|
|
10
|
+
- ✅ Type hints đầy đủ
|
|
11
|
+
|
|
12
|
+
## 📦 Cài đặt
|
|
13
|
+
|
|
14
|
+
### Cài đặt cơ bản
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install vnqr
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Hoặc sử dụng `uv`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv pip install vnqr
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 🚀 Sử dụng nhanh
|
|
27
|
+
|
|
28
|
+
### Tạo chuỗi VietQR
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from vnqr import generate_vietqr_string
|
|
32
|
+
|
|
33
|
+
vietqr_string = generate_vietqr_string(
|
|
34
|
+
bank_bin="970425", # Mã BIN của ngân hàng
|
|
35
|
+
bank_account="123456789", # Số tài khoản
|
|
36
|
+
total_amount=100000, # Số tiền (VNĐ)
|
|
37
|
+
content="Thanh toan don hang" # Nội dung giao dịch
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
print(vietqr_string)
|
|
41
|
+
# Output: 00020101021238530010A0000007270123000697042501091234567890208QRIBFTTA5303704540710000005802VN62150811Thanh toan don hang6304...
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Tạo hình ảnh QR code
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
48
|
+
import base64
|
|
49
|
+
|
|
50
|
+
# Tạo chuỗi VietQR
|
|
51
|
+
vietqr_string = generate_vietqr_string(
|
|
52
|
+
bank_bin="970425",
|
|
53
|
+
bank_account="123456789",
|
|
54
|
+
total_amount=100000,
|
|
55
|
+
content="Thanh toan don hang"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Tạo hình ảnh QR code (dạng base64)
|
|
59
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
60
|
+
|
|
61
|
+
# Lưu vào file
|
|
62
|
+
image_data = base64.b64decode(qr_image_base64)
|
|
63
|
+
with open("vietqr.png", "wb") as f:
|
|
64
|
+
f.write(image_data)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Tạo QR code với icon
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import io
|
|
71
|
+
from vnqr import generate_vietqr_string, generate_qr_image_with_icon
|
|
72
|
+
|
|
73
|
+
# Tạo chuỗi VietQR
|
|
74
|
+
qr_string = generate_vietqr_string(
|
|
75
|
+
bank_bin="970425",
|
|
76
|
+
bank_account="123456789",
|
|
77
|
+
total_amount=100000,
|
|
78
|
+
content="Thanh toan don hang",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Cách 1: truyền đường dẫn file icon
|
|
82
|
+
qr_with_logo = generate_qr_image_with_icon(
|
|
83
|
+
qr_string=qr_string,
|
|
84
|
+
icon_path="examples/logo.png",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Cách 2: đọc icon thành bytes rồi wrap bằng BytesIO
|
|
88
|
+
with open("examples/logo.png", "rb") as f:
|
|
89
|
+
icon_bytes = f.read()
|
|
90
|
+
|
|
91
|
+
qr_with_logo_from_bytes = generate_qr_image_with_icon(
|
|
92
|
+
qr_string=qr_string,
|
|
93
|
+
icon_path=io.BytesIO(icon_bytes), # BinaryIO được hỗ trợ
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 📚 API Reference
|
|
98
|
+
|
|
99
|
+
### `generate_vietqr_string()`
|
|
100
|
+
|
|
101
|
+
Tạo chuỗi VietQR theo tiêu chuẩn NAPAS.
|
|
102
|
+
|
|
103
|
+
**Tham số:**
|
|
104
|
+
- `bank_bin` (str): Mã BIN của ngân hàng (6 chữ số). Ví dụ: "970425" (Techcombank)
|
|
105
|
+
- `bank_account` (str): Số tài khoản ngân hàng
|
|
106
|
+
- `total_amount` (int): Số tiền giao dịch (VNĐ)
|
|
107
|
+
- `content` (str): Nội dung giao dịch
|
|
108
|
+
|
|
109
|
+
**Trả về:**
|
|
110
|
+
- `str`: Chuỗi VietQR
|
|
111
|
+
|
|
112
|
+
**Ví dụ:**
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from vnqr import generate_vietqr_string
|
|
116
|
+
|
|
117
|
+
vietqr_string = generate_vietqr_string(
|
|
118
|
+
bank_bin="970425",
|
|
119
|
+
bank_account="123456789",
|
|
120
|
+
total_amount=100000,
|
|
121
|
+
content="Thanh toan don hang"
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `generate_qr_image()`
|
|
126
|
+
|
|
127
|
+
Tạo hình ảnh QR code từ chuỗi VietQR.
|
|
128
|
+
|
|
129
|
+
**Tham số:**
|
|
130
|
+
- `qr_string` (str): Chuỗi VietQR
|
|
131
|
+
|
|
132
|
+
**Trả về:**
|
|
133
|
+
- `str`: Hình ảnh QR code dạng base64
|
|
134
|
+
|
|
135
|
+
**Ví dụ:**
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from vnqr import generate_qr_image
|
|
139
|
+
|
|
140
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `generate_qr_image_with_icon()`
|
|
144
|
+
|
|
145
|
+
Tạo hình ảnh QR code với icon đặt giữa (ví dụ logo thương hiệu).
|
|
146
|
+
|
|
147
|
+
**Tham số:**
|
|
148
|
+
- `qr_string` (str): Chuỗi VietQR
|
|
149
|
+
- `icon_path` (str | Path | BinaryIO): Đường dẫn tới icon hoặc đối tượng file-like (có `.read()` trả về bytes). Có thể đọc file ảnh vào bytes rồi truyền `io.BytesIO(icon_bytes)`.
|
|
150
|
+
- `icon_size_ratio` (float): Tỷ lệ kích thước icon so với QR (mặc định 0.25)
|
|
151
|
+
- `error_correction` (int): Mức sửa lỗi QR, mặc định `qrcode.ERROR_CORRECT_H`
|
|
152
|
+
|
|
153
|
+
**Trả về:**
|
|
154
|
+
- `str`: Hình ảnh QR code dạng base64
|
|
155
|
+
|
|
156
|
+
**Ví dụ:**
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
import io
|
|
160
|
+
from vnqr import generate_qr_image_with_icon
|
|
161
|
+
|
|
162
|
+
with open("logo.png", "rb") as f:
|
|
163
|
+
icon_bytes = f.read()
|
|
164
|
+
|
|
165
|
+
qr_image_base64 = generate_qr_image_with_icon(
|
|
166
|
+
qr_string=vietqr_string,
|
|
167
|
+
icon_path=io.BytesIO(icon_bytes),
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 💡 Examples / Ví dụ sử dụng
|
|
172
|
+
|
|
173
|
+
### Example 1: Sử dụng đơn giản nhất
|
|
174
|
+
|
|
175
|
+
Xem file [`examples/simple_example.py`](examples/simple_example.py):
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
179
|
+
|
|
180
|
+
qr_string = generate_vietqr_string(
|
|
181
|
+
bank_bin="970425",
|
|
182
|
+
bank_account="123456789",
|
|
183
|
+
total_amount=100000,
|
|
184
|
+
content="Thanh toan don hang"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
qr_image = generate_qr_image(qr_string)
|
|
188
|
+
print("VietQR String:", qr_string)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Chạy ví dụ:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
python examples/simple_example.py
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Example 2: Sử dụng cơ bản với HTML (xem trực quan QR)
|
|
198
|
+
|
|
199
|
+
Xem file [`examples/basic_usage.py`](examples/basic_usage.py):
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
203
|
+
|
|
204
|
+
# Tạo chuỗi VietQR
|
|
205
|
+
vietqr_string = generate_vietqr_string(
|
|
206
|
+
bank_bin="970425",
|
|
207
|
+
bank_account="123456789",
|
|
208
|
+
total_amount=100000,
|
|
209
|
+
content="Thanh toan don hang"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Tạo QR code image
|
|
213
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
214
|
+
|
|
215
|
+
# Lưu vào file HTML để xem
|
|
216
|
+
# ... (xem file example để biết chi tiết)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Chạy ví dụ:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
python examples/basic_usage.py
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Kết quả:
|
|
226
|
+
|
|
227
|
+
- File `vietqr_example.html` được tạo trong thư mục `examples/`
|
|
228
|
+
- Mở file này bằng trình duyệt, bạn sẽ thấy:
|
|
229
|
+
- **Ảnh QR code** có thể quét bằng app ngân hàng
|
|
230
|
+
- **Thông tin giao dịch**: BIN, số tài khoản, số tiền, nội dung
|
|
231
|
+
- **Chuỗi VietQR** đầy đủ hiển thị bên dưới ảnh
|
|
232
|
+
|
|
233
|
+
### Example 3: Lưu QR code vào file
|
|
234
|
+
|
|
235
|
+
Xem file [`examples/save_qr_image.py`](examples/save_qr_image.py):
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
import base64
|
|
239
|
+
from vnqr import generate_vietqr_string, generate_qr_image
|
|
240
|
+
|
|
241
|
+
vietqr_string = generate_vietqr_string(
|
|
242
|
+
bank_bin="970422",
|
|
243
|
+
bank_account="987654321",
|
|
244
|
+
total_amount=500000,
|
|
245
|
+
content="Noi dung chuyen khoan"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
qr_image_base64 = generate_qr_image(vietqr_string)
|
|
249
|
+
|
|
250
|
+
# Lưu vào file PNG
|
|
251
|
+
image_data = base64.b64decode(qr_image_base64)
|
|
252
|
+
with open("vietqr_code.png", "wb") as f:
|
|
253
|
+
f.write(image_data)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Chạy ví dụ:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
python examples/save_qr_image.py
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Kết quả:
|
|
263
|
+
|
|
264
|
+
- File ảnh `vietqr_code.png` được tạo trong thư mục `examples/`
|
|
265
|
+
- Đây là QR code hoàn chỉnh, có thể mở trực tiếp bằng trình xem ảnh hoặc quét bằng ứng dụng ngân hàng
|
|
266
|
+
|
|
267
|
+
📸 **Ảnh minh họa QR code tạo từ ví dụ:**
|
|
268
|
+
|
|
269
|
+

|
|
270
|
+
|
|
271
|
+
### Example 4: Crawl danh sách ngân hàng từ API VietQR
|
|
272
|
+
|
|
273
|
+
Xem file [`examples/crawl_banks.py`](examples/crawl_banks.py):
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
from vnqr.tools import crawl_bank_info
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def main():
|
|
280
|
+
crawl_bank_info(output_file="banks.json")
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Chạy ví dụ:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
python examples/crawl_banks.py
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Kết quả:
|
|
290
|
+
|
|
291
|
+
- Gọi API `https://api.vietqr.io/v2/banks` để lấy danh sách ngân hàng mới nhất
|
|
292
|
+
- Lưu dữ liệu vào file `banks.json` (bao gồm mã BIN, tên ngân hàng, trạng thái hỗ trợ chuyển khoản…)
|
|
293
|
+
- In thống kê nhanh: tổng số ngân hàng, bao nhiêu ngân hàng hỗ trợ chuyển khoản, liệt kê vài ngân hàng đầu tiên
|
|
294
|
+
|
|
295
|
+
Bạn cũng có thể chạy trực tiếp module tiện ích:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
python -m vnqr.tools.craw_bank
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## 🏦 Mã BIN của các ngân hàng phổ biến
|
|
302
|
+
|
|
303
|
+
Dưới đây là một số mã BIN phổ biến (6 chữ số):
|
|
304
|
+
|
|
305
|
+
| Ngân hàng | Mã BIN |
|
|
306
|
+
|-----------|--------|
|
|
307
|
+
| Techcombank | 970425 |
|
|
308
|
+
| Vietinbank | 970422 |
|
|
309
|
+
| Vietcombank | 970436 |
|
|
310
|
+
| BIDV | 970418 |
|
|
311
|
+
| Agribank | 970405 |
|
|
312
|
+
| ACB | 970416 |
|
|
313
|
+
| VPBank | 970432 |
|
|
314
|
+
| TPBank | 970423 |
|
|
315
|
+
| MBBank | 970422 |
|
|
316
|
+
|
|
317
|
+
> **Lưu ý:** Mã BIN có thể khác nhau tùy theo loại thẻ. Vui lòng kiểm tra với ngân hàng của bạn để có mã BIN chính xác.
|
|
318
|
+
|
|
319
|
+
## 🔧 Yêu cầu hệ thống
|
|
320
|
+
|
|
321
|
+
- Python >= 3.10
|
|
322
|
+
- Dependencies:
|
|
323
|
+
- `crcmod >= 1.7` - Tính toán CRC
|
|
324
|
+
- `pypng >= 0.20220715.0` - Xử lý hình ảnh PNG
|
|
325
|
+
- `qrcode >= 8.2` - Tạo QR code
|
|
326
|
+
|
|
327
|
+
## 📝 Cấu trúc dự án
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
vnqr/
|
|
331
|
+
├── src/
|
|
332
|
+
│ └── vnqr/
|
|
333
|
+
│ ├── __init__.py
|
|
334
|
+
│ ├── constants.py # Các hằng số VietQR
|
|
335
|
+
│ ├── crc.py # Tính toán CRC
|
|
336
|
+
│ ├── element.py # Class Element
|
|
337
|
+
│ ├── qr.py # Tạo QR code image
|
|
338
|
+
│ └── vietqr.py # Tạo chuỗi VietQR
|
|
339
|
+
├── examples/ # Các ví dụ sử dụng
|
|
340
|
+
├── tests/ # Unit tests
|
|
341
|
+
├── pyproject.toml
|
|
342
|
+
└── README.md
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## 🧪 Testing
|
|
346
|
+
|
|
347
|
+
Chạy tests:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
pytest
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Với coverage:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
pytest --cov=vnqr
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## 🤝 Đóng góp
|
|
360
|
+
|
|
361
|
+
Mọi đóng góp đều được chào đón! Vui lòng:
|
|
362
|
+
|
|
363
|
+
1. Fork dự án
|
|
364
|
+
2. Tạo branch cho feature mới (`git checkout -b feature/AmazingFeature`)
|
|
365
|
+
3. Commit các thay đổi (`git commit -m 'Add some AmazingFeature'`)
|
|
366
|
+
4. Push lên branch (`git push origin feature/AmazingFeature`)
|
|
367
|
+
5. Mở Pull Request
|
|
368
|
+
|
|
369
|
+
## 📄 License
|
|
370
|
+
|
|
371
|
+
Dự án này được phân phối dưới giấy phép MIT. Xem file [LICENSE](LICENSE) để biết thêm chi tiết.
|
|
372
|
+
|
|
373
|
+
## 👤 Author
|
|
374
|
+
|
|
375
|
+
**Do Quoc Vuong**
|
|
376
|
+
- Email: vuongtlt13@gmail.com
|
|
377
|
+
- GitHub: [@vuongtlt13](https://github.com/vuongtlt13)
|
|
378
|
+
|
|
379
|
+
## 🔗 Links
|
|
380
|
+
|
|
381
|
+
- [Homepage](https://github.com/vuongtlt13/vietqr)
|
|
382
|
+
- [Issues](https://github.com/vuongtlt13/vietqr/issues)
|
|
383
|
+
- [License](https://opensource.org/licenses/MIT)
|
|
384
|
+
|
|
385
|
+
## ⚠️ Lưu ý
|
|
386
|
+
|
|
387
|
+
- Package này tạo VietQR code theo tiêu chuẩn NAPAS
|
|
388
|
+
- Mã BIN cần phải chính xác để QR code hoạt động đúng
|
|
389
|
+
- Đảm bảo số tiền và thông tin giao dịch là hợp lệ
|
|
390
|
+
- QR code được tạo là QR động (one-time use)
|
|
391
|
+
|
|
392
|
+
## 🙏 Acknowledgments
|
|
393
|
+
|
|
394
|
+
- NAPAS - Tiêu chuẩn VietQR
|
|
395
|
+
- Thư viện [qrcode](https://github.com/lincolnloop/python-qrcode) - Tạo QR code
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
Made with ❤️ in Vietnam
|
|
400
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vnqr"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Thư viện Python để tạo mã QR thanh toán VietQR theo tiêu chuẩn NAPAS, hỗ trợ tạo hình ảnh QR code và thêm icon tùy chỉnh"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Do Quoc Vuong", email = "vuongtlt13@gmail.com" },
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
|
15
|
+
keywords = ['vietqr', 'qr-code', 'bank', 'vietnam', 'Viet Nam', 'qr']
|
|
16
|
+
dependencies = [
|
|
17
|
+
"crcmod>=1.7",
|
|
18
|
+
"pypng>=0.20220715.0",
|
|
19
|
+
"qrcode>=8.2",
|
|
20
|
+
"requests>=2.32.5",
|
|
21
|
+
"pillow>=10.0.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/vuongtlt13/vietqr"
|
|
26
|
+
Issues = "https://github.com/vuongtlt13/vietqr/issues"
|
|
27
|
+
License = "https://opensource.org/licenses/MIT"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build >= 0.9.9, <0.10.0"]
|
|
31
|
+
build-backend = "uv_build"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"black>=25.11.0",
|
|
36
|
+
"coverage>=7.12.0",
|
|
37
|
+
"isort>=7.0.0",
|
|
38
|
+
"mypy>=1.18.2",
|
|
39
|
+
"pre-commit>=4.5.0",
|
|
40
|
+
"pytest>=9.0.1",
|
|
41
|
+
"pytest-asyncio>=1.3.0",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
pythonpath = ["src"]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
addopts = "-vv --strict-config --strict-markers"
|
|
48
|
+
asyncio_mode = "auto"
|
|
49
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
50
|
+
|
|
51
|
+
[tool.coverage.run]
|
|
52
|
+
branch = true
|
|
53
|
+
source = ["vnqr"]
|
|
54
|
+
omit = [
|
|
55
|
+
"src/tests/*",
|
|
56
|
+
"src/**/__init__.py",
|
|
57
|
+
"src/examples/*",
|
|
58
|
+
"src/vnqr/tools/*",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.coverage.report]
|
|
62
|
+
show_missing = true
|
|
63
|
+
skip_covered = true
|
|
64
|
+
fail_under = 0
|
|
65
|
+
exclude_lines = []
|
|
66
|
+
|
|
67
|
+
[tool.black]
|
|
68
|
+
line-length = 88
|
|
69
|
+
target-version = ['py310']
|
|
70
|
+
|
|
71
|
+
[tool.isort]
|
|
72
|
+
profile = "black"
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
exclude = '^(?!src/vnqr/).*'
|
|
76
|
+
|
|
77
|
+
python_version = "3.10"
|
|
78
|
+
ignore_missing_imports = true
|
|
79
|
+
disallow_untyped_calls = false
|
|
80
|
+
disable_error_code = ["no-untyped-def"]
|
|
81
|
+
strict = true
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .vietqr import generate_vietqr_string
|
|
2
|
+
from .qr import generate_qr_image, generate_qr_image_with_icon
|
|
3
|
+
from .tools import crawl_bank_info, fetch_banks
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"generate_vietqr_string",
|
|
7
|
+
"generate_qr_image",
|
|
8
|
+
"generate_qr_image_with_icon",
|
|
9
|
+
"crawl_bank_info",
|
|
10
|
+
"fetch_banks",
|
|
11
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class VietQRField:
|
|
5
|
+
# Phiên bản dữ liệu
|
|
6
|
+
PAYLOAD_FORMAT_INDICATOR = "00"
|
|
7
|
+
|
|
8
|
+
# Phương thức khởi tạo
|
|
9
|
+
INITIATION_METHOD = "01"
|
|
10
|
+
|
|
11
|
+
# Thông tin định danh ĐVCNTT
|
|
12
|
+
MERCHANT_ACCOUNT_INFO = "38"
|
|
13
|
+
|
|
14
|
+
# Mã danh mục ĐVCNTT
|
|
15
|
+
MERCHANT_CATEGORY_CODE = "52"
|
|
16
|
+
|
|
17
|
+
# Mã tiền tệ
|
|
18
|
+
TRANSACTION_CURRENCY = "53"
|
|
19
|
+
|
|
20
|
+
# Số tiền GD
|
|
21
|
+
TRANSACTION_AMOUNT = "54"
|
|
22
|
+
|
|
23
|
+
# Chỉ thị cho Tip và Phí GD
|
|
24
|
+
CONVENIENCE_INDICATOR = "55"
|
|
25
|
+
|
|
26
|
+
# Mã quốc gia
|
|
27
|
+
COUNTRY_CODE = "58"
|
|
28
|
+
|
|
29
|
+
# Thông tin bổ sung
|
|
30
|
+
ADDITIONAL_DATA = "62"
|
|
31
|
+
|
|
32
|
+
CRC = "63"
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def all(cls) -> List[str]:
|
|
36
|
+
return [
|
|
37
|
+
getattr(cls, attr)
|
|
38
|
+
for attr in dir(cls)
|
|
39
|
+
if not callable(getattr(cls, attr)) and not attr.startswith("__")
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ID = 38
|
|
44
|
+
class VietQRMerchantInfoField:
|
|
45
|
+
# Định danh duy nhất toàn cầu - GUID
|
|
46
|
+
GUID = "00"
|
|
47
|
+
|
|
48
|
+
# Tổ chức thụ hưởng(NHTV,TGTT)
|
|
49
|
+
ORG_INFO = "01"
|
|
50
|
+
|
|
51
|
+
# Mã dịch vụ
|
|
52
|
+
SERVICE_CODE = "02"
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def all(cls) -> List[str]:
|
|
56
|
+
return [
|
|
57
|
+
getattr(cls, attr)
|
|
58
|
+
for attr in dir(cls)
|
|
59
|
+
if not callable(getattr(cls, attr)) and not attr.startswith("__")
|
|
60
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import crcmod
|
|
2
|
+
|
|
3
|
+
_crc_func = crcmod.mkCrcFun(0x11021, initCrc=0xFFFF, xorOut=0x0000, rev=False)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_final_crc_hex_string(hex_str: str):
|
|
7
|
+
return hex_str.upper()[2:].zfill(4)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def crc_func(data: str | bytes) -> str:
|
|
11
|
+
if isinstance(data, str):
|
|
12
|
+
return get_final_crc_hex_string(hex(_crc_func(data.encode())).upper())
|
|
13
|
+
|
|
14
|
+
return get_final_crc_hex_string(hex(_crc_func(data)).upper())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Element:
|
|
5
|
+
def __init__(self, element_id: str, value: int | str):
|
|
6
|
+
assert value is not None, "value must be int or str"
|
|
7
|
+
if isinstance(value, str):
|
|
8
|
+
assert len(value) > 0, "length of value must be greater than zero"
|
|
9
|
+
self.element_id = element_id
|
|
10
|
+
assert len(element_id) <= 2, "length of element id must be less than 2"
|
|
11
|
+
self.value = value
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def value_length(self) -> int:
|
|
15
|
+
return len(str(self.value))
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"{self.element_id}{self.value_length:02}{self.value}"
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def parse(cls, text: str, strict=True) -> Tuple[str, str | int]:
|
|
22
|
+
element_id, value = cls.try_extract(
|
|
23
|
+
element_id=text[:2], text=text, strict=strict
|
|
24
|
+
)
|
|
25
|
+
if element_id is not None and value is not None:
|
|
26
|
+
return element_id, value
|
|
27
|
+
raise ValueError(f"Invalid format! Can't parse `{text}`")
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_text(cls, text: str, strict=True) -> "Element":
|
|
31
|
+
element_id, value = cls.parse(text, strict=strict)
|
|
32
|
+
return cls(element_id=element_id, value=value)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def try_extract(
|
|
36
|
+
cls, element_id: str, text: str, strict=True
|
|
37
|
+
) -> Tuple[str, str | int | None]:
|
|
38
|
+
try:
|
|
39
|
+
assert element_id.isdigit(), "element_id must be digits"
|
|
40
|
+
length = int(text[2:4])
|
|
41
|
+
value = text[4:]
|
|
42
|
+
if strict:
|
|
43
|
+
assert length == len(value), "length not match value length!"
|
|
44
|
+
|
|
45
|
+
return element_id, value[:length]
|
|
46
|
+
except:
|
|
47
|
+
return element_id, None
|
|
48
|
+
|
|
49
|
+
def __eq__(self, other: "Element"):
|
|
50
|
+
return (
|
|
51
|
+
isinstance(self, other.__class__)
|
|
52
|
+
and (self.element_id == other.element_id)
|
|
53
|
+
and (str(self.value) == str(other.value))
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def tuple(self):
|
|
57
|
+
return (
|
|
58
|
+
self.element_id,
|
|
59
|
+
self.value,
|
|
60
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import BinaryIO
|
|
5
|
+
|
|
6
|
+
import qrcode
|
|
7
|
+
from qrcode import ERROR_CORRECT_H, ERROR_CORRECT_L
|
|
8
|
+
from qrcode.image.pure import PyPNGImage
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from PIL import Image
|
|
12
|
+
except ImportError:
|
|
13
|
+
Image = None # type: ignore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def generate_qr_image(
|
|
17
|
+
qr_string: str,
|
|
18
|
+
box_size: int = 5,
|
|
19
|
+
border: int = 2,
|
|
20
|
+
) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Tạo QR code image từ chuỗi
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
qr_string: Chuỗi để tạo QR code
|
|
26
|
+
box_size: Kích thước mỗi ô vuông (pixel). Mặc định: 5
|
|
27
|
+
border: Kích thước viền xung quanh (số ô). Mặc định: 2
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: QR code image dạng base64
|
|
31
|
+
|
|
32
|
+
Note:
|
|
33
|
+
Kích thước QR code = (số ô × box_size) + (border × 2 × box_size)
|
|
34
|
+
Ví dụ: QR 25x25 ô với box_size=5, border=2
|
|
35
|
+
=> (25 × 5) + (2 × 2 × 5) = 125 + 20 = 145 pixels
|
|
36
|
+
"""
|
|
37
|
+
img = qrcode.make(
|
|
38
|
+
qr_string,
|
|
39
|
+
image_factory=PyPNGImage,
|
|
40
|
+
error_correction=ERROR_CORRECT_L,
|
|
41
|
+
box_size=box_size,
|
|
42
|
+
border=border,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
f = io.BytesIO()
|
|
46
|
+
|
|
47
|
+
img.save(f)
|
|
48
|
+
|
|
49
|
+
f.seek(0)
|
|
50
|
+
|
|
51
|
+
image_content = f.read()
|
|
52
|
+
|
|
53
|
+
base64_utf8_str = base64.b64encode(image_content).decode("utf-8")
|
|
54
|
+
|
|
55
|
+
return base64_utf8_str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def generate_qr_image_with_icon(
|
|
59
|
+
qr_string: str,
|
|
60
|
+
icon_path: str | Path | BinaryIO,
|
|
61
|
+
icon_size_ratio: float = 0.25,
|
|
62
|
+
error_correction: int = ERROR_CORRECT_H,
|
|
63
|
+
) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Tạo QR code image với icon ở giữa
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
qr_string: Chuỗi để tạo QR code
|
|
69
|
+
icon_path: Đường dẫn đến file icon (PNG, JPG, v.v.) hoặc file-like object
|
|
70
|
+
icon_size_ratio: Tỷ lệ kích thước icon so với QR code (0.1 - 0.3 khuyến nghị)
|
|
71
|
+
error_correction: Mức độ sửa lỗi (ERROR_CORRECT_L, M, Q, H). Mặc định H (cao nhất)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
str: QR code image dạng base64
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ImportError: Nếu Pillow chưa được cài đặt
|
|
78
|
+
ValueError: Nếu icon_path không hợp lệ
|
|
79
|
+
"""
|
|
80
|
+
if Image is None:
|
|
81
|
+
raise ImportError(
|
|
82
|
+
"Pillow (PIL) is required for generating QR code with icon. "
|
|
83
|
+
"Please install it: pip install Pillow"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Tạo QR code với error correction cao hơn để vẫn scan được khi có icon
|
|
87
|
+
qr = qrcode.QRCode(
|
|
88
|
+
version=1,
|
|
89
|
+
error_correction=error_correction,
|
|
90
|
+
box_size=10,
|
|
91
|
+
border=4,
|
|
92
|
+
)
|
|
93
|
+
qr.add_data(qr_string)
|
|
94
|
+
qr.make(fit=True)
|
|
95
|
+
|
|
96
|
+
# Tạo QR code image
|
|
97
|
+
qr_img = qr.make_image(fill_color="black", back_color="white").convert("RGB")
|
|
98
|
+
|
|
99
|
+
# Mở và resize icon
|
|
100
|
+
if isinstance(icon_path, (str, Path)):
|
|
101
|
+
icon = Image.open(icon_path)
|
|
102
|
+
else:
|
|
103
|
+
# File-like object
|
|
104
|
+
icon = Image.open(icon_path)
|
|
105
|
+
|
|
106
|
+
# Convert icon sang RGBA nếu cần (để hỗ trợ transparency)
|
|
107
|
+
if icon.mode != "RGBA":
|
|
108
|
+
icon = icon.convert("RGBA")
|
|
109
|
+
|
|
110
|
+
# Tính toán kích thước icon
|
|
111
|
+
qr_width, qr_height = qr_img.size
|
|
112
|
+
icon_size = (
|
|
113
|
+
int(qr_width * icon_size_ratio),
|
|
114
|
+
int(qr_height * icon_size_ratio),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Resize icon (giữ tỷ lệ)
|
|
118
|
+
# Sử dụng LANCZOS resampling (có thể là Image.Resampling.LANCZOS hoặc Image.LANCZOS)
|
|
119
|
+
try:
|
|
120
|
+
resampling = Image.Resampling.LANCZOS # Pillow >= 10.0.0
|
|
121
|
+
except AttributeError:
|
|
122
|
+
resampling = Image.LANCZOS # Pillow < 10.0.0
|
|
123
|
+
icon.thumbnail(icon_size, resampling)
|
|
124
|
+
|
|
125
|
+
# Tạo một image mới với background trong suốt có cùng kích thước
|
|
126
|
+
icon_with_bg = Image.new("RGBA", icon_size, (255, 255, 255, 0))
|
|
127
|
+
|
|
128
|
+
# Paste icon vào giữa background
|
|
129
|
+
icon_x = (icon_size[0] - icon.size[0]) // 2
|
|
130
|
+
icon_y = (icon_size[1] - icon.size[1]) // 2
|
|
131
|
+
icon_with_bg.paste(icon, (icon_x, icon_y), icon)
|
|
132
|
+
|
|
133
|
+
# Tính toán vị trí để paste icon vào giữa QR code
|
|
134
|
+
qr_x = (qr_width - icon_size[0]) // 2
|
|
135
|
+
qr_y = (qr_height - icon_size[1]) // 2
|
|
136
|
+
|
|
137
|
+
# Paste icon vào QR code
|
|
138
|
+
qr_img.paste(icon_with_bg, (qr_x, qr_y), icon_with_bg)
|
|
139
|
+
|
|
140
|
+
# Convert về BytesIO và encode base64
|
|
141
|
+
f = io.BytesIO()
|
|
142
|
+
qr_img.save(f, format="PNG")
|
|
143
|
+
f.seek(0)
|
|
144
|
+
image_content = f.read()
|
|
145
|
+
base64_utf8_str = base64.b64encode(image_content).decode("utf-8")
|
|
146
|
+
|
|
147
|
+
return base64_utf8_str
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Crawl thông tin ngân hàng từ API VietQR
|
|
3
|
+
Lấy danh sách ngân hàng và lưu vào file banks.json
|
|
4
|
+
|
|
5
|
+
Cách sử dụng:
|
|
6
|
+
pip install requests
|
|
7
|
+
python src/vnqr/tools/craw_bank.py
|
|
8
|
+
|
|
9
|
+
Hoặc:
|
|
10
|
+
python -m vnqr.tools.craw_bank
|
|
11
|
+
|
|
12
|
+
API: https://api.vietqr.io/v2/banks
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import requests
|
|
21
|
+
except Exception:
|
|
22
|
+
print("Module requests not installed!")
|
|
23
|
+
raise
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fetch_banks() -> dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Gọi API để lấy danh sách ngân hàng từ VietQR API
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
dict: Dữ liệu JSON từ API
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
requests.RequestException: Nếu có lỗi khi gọi API
|
|
35
|
+
"""
|
|
36
|
+
url = "https://api.vietqr.io/v2/banks"
|
|
37
|
+
|
|
38
|
+
print(f"🔄 Đang gọi API: {url}")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = requests.get(url, timeout=10)
|
|
42
|
+
response.raise_for_status() # Raise exception nếu status code không phải 2xx
|
|
43
|
+
|
|
44
|
+
data = response.json()
|
|
45
|
+
|
|
46
|
+
if data.get("code") == "00":
|
|
47
|
+
print(f"✅ Lấy dữ liệu thành công! Tổng cộng {len(data.get('data', []))} ngân hàng")
|
|
48
|
+
else:
|
|
49
|
+
print(f"⚠️ API trả về code: {data.get('code')}, desc: {data.get('desc')}")
|
|
50
|
+
|
|
51
|
+
return data
|
|
52
|
+
|
|
53
|
+
except requests.Timeout:
|
|
54
|
+
raise requests.RequestException("⏱️ Timeout khi gọi API. Vui lòng thử lại.")
|
|
55
|
+
except requests.RequestException as e:
|
|
56
|
+
raise requests.RequestException(f"❌ Lỗi khi gọi API: {str(e)}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def save_banks_to_file(data: dict[str, Any], output_file: str) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Lưu dữ liệu ngân hàng vào file JSON
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
data: Dữ liệu từ API
|
|
65
|
+
output_file: Tên file output (mặc định: banks.json)
|
|
66
|
+
"""
|
|
67
|
+
output_path = Path(output_file)
|
|
68
|
+
|
|
69
|
+
print(f"💾 Đang lưu dữ liệu vào file: {output_path.absolute()}")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
73
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
74
|
+
|
|
75
|
+
print(f"✅ Đã lưu thành công vào {output_path.absolute()}")
|
|
76
|
+
|
|
77
|
+
# In thống kê
|
|
78
|
+
banks = data.get("data", [])
|
|
79
|
+
if banks:
|
|
80
|
+
print(f"\n📊 Thống kê:")
|
|
81
|
+
print(f" - Tổng số ngân hàng: {len(banks)}")
|
|
82
|
+
|
|
83
|
+
# Đếm số ngân hàng hỗ trợ transfer
|
|
84
|
+
transfer_supported = sum(1 for bank in banks if bank.get("transferSupported") == 1)
|
|
85
|
+
print(f" - Ngân hàng hỗ trợ chuyển khoản: {transfer_supported}")
|
|
86
|
+
|
|
87
|
+
# In một vài ngân hàng đầu tiên
|
|
88
|
+
print(f"\n📋 Một số ngân hàng:")
|
|
89
|
+
for bank in banks[:5]:
|
|
90
|
+
print(f" - {bank.get('shortName')} ({bank.get('code')}): BIN {bank.get('bin')}")
|
|
91
|
+
|
|
92
|
+
if len(banks) > 5:
|
|
93
|
+
print(f" ... và {len(banks) - 5} ngân hàng khác")
|
|
94
|
+
|
|
95
|
+
except IOError as e:
|
|
96
|
+
raise IOError(f"❌ Lỗi khi ghi file: {str(e)}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def crawl_bank_info(output_file: str="banks.json") -> None:
|
|
100
|
+
"""Hàm main để chạy script"""
|
|
101
|
+
try:
|
|
102
|
+
# Gọi API lấy dữ liệu
|
|
103
|
+
data = fetch_banks()
|
|
104
|
+
|
|
105
|
+
# Lưu vào file
|
|
106
|
+
save_banks_to_file(data, output_file=output_file)
|
|
107
|
+
|
|
108
|
+
print("\n🎉 Hoàn thành!")
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"\n❌ Có lỗi xảy ra: {str(e)}")
|
|
112
|
+
raise
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from .qr import generate_qr_image
|
|
2
|
+
from .constants import VietQRField, VietQRMerchantInfoField
|
|
3
|
+
from .crc import crc_func
|
|
4
|
+
from .element import Element
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_vietqr_string(
|
|
8
|
+
bank_bin: str,
|
|
9
|
+
bank_account: str,
|
|
10
|
+
total_amount: int,
|
|
11
|
+
content: str,
|
|
12
|
+
) -> str:
|
|
13
|
+
# Phiên bản dữ liệu (ID 00)
|
|
14
|
+
# Phiên bản dữ liệu có giá trị là “01”.
|
|
15
|
+
version_element = Element(
|
|
16
|
+
element_id=VietQRField.PAYLOAD_FORMAT_INDICATOR, value="01"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Phương thức khởi tạo (ID 01)
|
|
20
|
+
# - “11” = QR tĩnh – áp dụng khi mã QR cho phép thực hiện nhiều lần giao dịch.
|
|
21
|
+
# - “12” = QR động – áp dụng khi mã QR chỉ cho phép thực hiện một lần giao dịch.
|
|
22
|
+
init_method = Element(element_id=VietQRField.INITIATION_METHOD, value="12")
|
|
23
|
+
|
|
24
|
+
# Thông tin định danh ĐVCNTT (ID 38)
|
|
25
|
+
# ID 38 được sử dụng cho dịch vụ VietQR qua hệ thống Napas.
|
|
26
|
+
|
|
27
|
+
# ID quy định dữ liệu với 1 trong các giá trị sau:
|
|
28
|
+
# AID: luôn có giá trị “A000000727”.
|
|
29
|
+
merchant_info_guid = Element(
|
|
30
|
+
element_id=VietQRMerchantInfoField.GUID, value="A000000727"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# merchant_info_org_info
|
|
34
|
+
merchant_info_org_info_acquirer = Element(element_id="00", value=bank_bin)
|
|
35
|
+
merchant_info_org_info_consumer = Element(element_id="01", value=bank_account)
|
|
36
|
+
merchant_info_org_info = Element(
|
|
37
|
+
element_id=VietQRMerchantInfoField.ORG_INFO,
|
|
38
|
+
value=f"{merchant_info_org_info_acquirer}{merchant_info_org_info_consumer}",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# merchant_info_service_code
|
|
42
|
+
# - QRIBFTTC: dịch vụ chuyển nhanh NAPAS247 bằng mã QR đến thẻ
|
|
43
|
+
# - QRIBFTTA: dịch vụ chuyển nhanh NAPAS247 bằng mã QR đến Tài khoản
|
|
44
|
+
merchant_info_service_code = Element(
|
|
45
|
+
element_id=VietQRMerchantInfoField.SERVICE_CODE,
|
|
46
|
+
value="QRIBFTTA",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
merchant_info = Element(
|
|
50
|
+
element_id=VietQRField.MERCHANT_ACCOUNT_INFO,
|
|
51
|
+
value=f"{merchant_info_guid}{merchant_info_org_info}{merchant_info_service_code}",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Mã tiền tệ (ID 53)
|
|
55
|
+
currency_code = Element(element_id=VietQRField.TRANSACTION_CURRENCY, value="704")
|
|
56
|
+
|
|
57
|
+
# Số tiền giao dịch (ID 54)
|
|
58
|
+
transaction_amount = Element(
|
|
59
|
+
element_id=VietQRField.TRANSACTION_AMOUNT, value=f"{total_amount}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Mã quốc gia (ID 58)
|
|
63
|
+
country_code = Element(element_id=VietQRField.COUNTRY_CODE, value="VN")
|
|
64
|
+
|
|
65
|
+
# Thông tin bổ sung (ID 62)
|
|
66
|
+
# Mục đích giao dịch (08)
|
|
67
|
+
additional_info_purpose = Element(element_id="08", value=f"{content}")
|
|
68
|
+
additional_info = Element(
|
|
69
|
+
element_id=VietQRField.ADDITIONAL_DATA, value=f"{additional_info_purpose}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Mã kiểm thử giá trị - CRC (ID 63)
|
|
73
|
+
before_crc_data = f"{version_element}{init_method}{merchant_info}{currency_code}{transaction_amount}{country_code}{additional_info}{VietQRField.CRC}04"
|
|
74
|
+
crc_element = Element(element_id=VietQRField.CRC, value=crc_func(before_crc_data))
|
|
75
|
+
return f"{version_element}{init_method}{merchant_info}{currency_code}{transaction_amount}{country_code}{additional_info}{crc_element}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def generate_vietqr_image(
|
|
79
|
+
bank_bin: str,
|
|
80
|
+
bank_account: str,
|
|
81
|
+
total_amount: int,
|
|
82
|
+
content: str,
|
|
83
|
+
) -> str:
|
|
84
|
+
vietqr_string = generate_vietqr_string(
|
|
85
|
+
bank_bin=bank_bin,
|
|
86
|
+
bank_account=bank_account,
|
|
87
|
+
total_amount=total_amount,
|
|
88
|
+
content=content,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return generate_qr_image(
|
|
92
|
+
qr_string=vietqr_string,
|
|
93
|
+
)
|