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 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
+ ![VietQR sample](examples/vietqr_code.png)
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
+ ![VietQR sample](examples/vietqr_code.png)
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,5 @@
1
+ from .craw_bank import crawl_bank_info, fetch_banks
2
+ __all__ = [
3
+ "crawl_bank_info",
4
+ "fetch_banks"
5
+ ]
@@ -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
+ )