Line data Source code
1 : // Copyright 2026 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'dart:convert';
5 :
6 : import 'package:app_finance/_classes/controller/encryption_handler.dart';
7 : import 'package:app_finance/_classes/storage/app_preferences.dart';
8 : import 'package:fints/fints.dart';
9 :
10 : // Connection details of a FinTS 4.1 bank access, kept between sessions
11 : // The PIN is never part of it - asked for on each connection
12 : class FinTsSettings {
13 : // Product registration ID of the Deutsche Kreditwirtschaft
14 : // TBD: inject on build `--dart-define=FINTS_PRODUCT_ID=...`
15 : static const defaultProductId = String.fromEnvironment('FINTS_PRODUCT_ID');
16 :
17 : static const namespaces = {
18 : '4.1.1': FinTs4Namespaces.v411,
19 : '4.1': FinTs4Namespaces.v41,
20 : };
21 :
22 : String url;
23 : String bankCode;
24 : String userId;
25 : String customerId;
26 : String productId;
27 : String version;
28 :
29 : /// Bank account (accountKey) to the uuid of the application's account
30 : final Map<String, String> links;
31 :
32 : /// Bank account (accountKey) to the last day transactions were imported for
33 : final Map<String, DateTime> imported;
34 :
35 0 : FinTsSettings({
36 : this.url = '',
37 : this.bankCode = '',
38 : this.userId = '',
39 : this.customerId = '',
40 : this.productId = defaultProductId,
41 : this.version = '4.1.1',
42 : Map<String, String>? links,
43 : Map<String, DateTime>? imported,
44 0 : }) : links = links ?? {},
45 0 : imported = imported ?? {};
46 :
47 0 : bool get isComplete => url.isNotEmpty && bankCode.isNotEmpty && userId.isNotEmpty && productId.isNotEmpty;
48 :
49 0 : FinTs4Namespaces get namespace => namespaces[version] ?? FinTs4Namespaces.v411;
50 :
51 0 : FinTsConfig toConfig() => FinTsConfig(
52 0 : url: Uri.parse(url.trim()),
53 0 : bankCode: bankCode.trim(),
54 0 : userId: userId.trim(),
55 0 : customerId: customerId.trim().isEmpty ? null : customerId.trim(),
56 0 : productId: productId.trim(),
57 : );
58 :
59 0 : static String accountKey(Account account) {
60 0 : final iban = account.iban?.replaceAll(' ', '').toUpperCase();
61 0 : if (iban != null && iban.isNotEmpty) {
62 : return iban;
63 : }
64 0 : return [account.bankCode, account.accountNumber ?? '', account.subAccount ?? ''].join(':');
65 : }
66 :
67 0 : Map<String, dynamic> toJson() => {
68 0 : 'url': url,
69 0 : 'bankCode': bankCode,
70 0 : 'userId': userId,
71 0 : 'customerId': customerId,
72 0 : 'productId': productId,
73 0 : 'version': version,
74 0 : 'links': links,
75 0 : 'imported': imported.map((key, value) => MapEntry(key, value.toIso8601String())),
76 : };
77 :
78 0 : factory FinTsSettings.fromJson(Map<String, dynamic> json) => FinTsSettings(
79 0 : url: json['url'] ?? '',
80 0 : bankCode: json['bankCode'] ?? '',
81 0 : userId: json['userId'] ?? '',
82 0 : customerId: json['customerId'] ?? '',
83 0 : productId: (json['productId'] as String?)?.isNotEmpty == true ? json['productId'] : defaultProductId,
84 0 : version: namespaces.containsKey(json['version']) ? json['version'] : '4.1.1',
85 0 : links: (json['links'] as Map<String, dynamic>? ?? {}).map((key, value) => MapEntry(key, value.toString())),
86 0 : imported: (json['imported'] as Map<String, dynamic>? ?? {}).map(
87 0 : (key, value) => MapEntry(key, DateTime.parse(value.toString())),
88 : ),
89 : );
90 :
91 0 : static FinTsSettings load() {
92 0 : final value = AppPreferences.get(AppPreferences.prefFinTs);
93 0 : if (value == null || value.isEmpty) {
94 0 : return FinTsSettings();
95 : }
96 : try {
97 0 : return FinTsSettings.fromJson(jsonDecode(value));
98 0 : } on FormatException {
99 0 : return FinTsSettings();
100 : }
101 : }
102 :
103 0 : Future<void> save() => AppPreferences.set(AppPreferences.prefFinTs, jsonEncode(toJson()));
104 :
105 0 : static String? loadState() {
106 0 : final value = AppPreferences.get(AppPreferences.prefFinTsState);
107 0 : if (value == null || value.isEmpty) {
108 : return null;
109 : }
110 : try {
111 0 : return EncryptionHandler.decrypt(value);
112 : } catch (_) {
113 : return null;
114 : }
115 : }
116 :
117 0 : static Future<void> saveState(String state) =>
118 0 : AppPreferences.set(AppPreferences.prefFinTsState, EncryptionHandler.encrypt(state));
119 :
120 0 : static Future<void> clearState() => AppPreferences.clear(AppPreferences.prefFinTsState);
121 : }
|