1 /*
2 * This file is part of KMyMoney, A Personal Finance Manager by KDE
3 * Copyright (C) 2014 Christian Dávid <christian-david@web.de>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "payeeidentifier/ibanbic/ibanbic.h"
20
21 #include <typeinfo>
22 #include <algorithm>
23 #include <gmpxx.h>
24
25 #include <QDebug>
26
27 #include "kmymoneyplugin.h"
28 #include "mymoney/mymoneyexception.h"
29 #include "plugins/ibanbicdata/ibanbicdataenums.h"
30
31 namespace payeeIdentifiers
32 {
33
34 const int ibanBic::ibanMaxLength = 30;
35
ibanBic()36 ibanBic::ibanBic()
37 {
38 }
39
ibanBic(const ibanBic & other)40 ibanBic::ibanBic(const ibanBic& other)
41 : payeeIdentifierData(other),
42 m_bic(other.m_bic),
43 m_iban(other.m_iban),
44 m_ownerName(other.m_ownerName)
45 {
46 }
47
operator ==(const payeeIdentifierData & other) const48 bool ibanBic::operator==(const payeeIdentifierData& other) const
49 {
50 try {
51 const ibanBic otherCasted = dynamic_cast<const ibanBic&>(other);
52 return operator==(otherCasted);
53 } catch (const std::bad_cast&) {
54 }
55 return false;
56 }
57
operator ==(const ibanBic & other) const58 bool ibanBic::operator==(const ibanBic& other) const
59 {
60 return (m_iban == other.m_iban && m_bic == other.m_bic && m_ownerName == other.m_ownerName);
61 }
62
clone() const63 ibanBic* ibanBic::clone() const
64 {
65 return (new ibanBic(*this));
66 }
67
createFromXml(const QDomElement & element) const68 ibanBic* ibanBic::createFromXml(const QDomElement& element) const
69 {
70 ibanBic* ident = new ibanBic;
71
72 ident->setBic(element.attribute("bic", QString()));
73 ident->setIban(element.attribute("iban", QString()));
74 ident->setOwnerName(element.attribute("ownerName", QString()));
75 return ident;
76 }
77
writeXML(QDomDocument & document,QDomElement & parent) const78 void ibanBic::writeXML(QDomDocument& document, QDomElement& parent) const
79 {
80 Q_UNUSED(document);
81 parent.setAttribute("iban", m_iban);
82
83 if (!m_bic.isEmpty())
84 parent.setAttribute("bic", m_bic);
85 if (!m_ownerName.isEmpty())
86 parent.setAttribute("ownerName", m_ownerName);
87 }
88
paperformatIban(const QString & separator) const89 QString ibanBic::paperformatIban(const QString& separator) const
90 {
91 return ibanToPaperformat(m_iban, separator);
92 }
93
setIban(const QString & iban)94 void ibanBic::setIban(const QString& iban)
95 {
96 m_iban = ibanToElectronic(iban);
97 }
98
setBic(const QString & bic)99 void ibanBic::setBic(const QString& bic)
100 {
101 m_bic = canonizeBic(bic);
102 }
103
canonizeBic(const QString & bic)104 QString ibanBic::canonizeBic(const QString& bic)
105 {
106 QString canonizedBic = bic.toUpper();
107
108 if (canonizedBic.length() == 11 && canonizedBic.endsWith(QLatin1String("XXX")))
109 canonizedBic = canonizedBic.left(8);
110 return canonizedBic;
111 }
112
fullStoredBic() const113 QString ibanBic::fullStoredBic() const
114 {
115 if (m_bic.length() == 8)
116 return (m_bic + QLatin1String("XXX"));
117 return m_bic;
118 }
119
institutionName() const120 QString ibanBic::institutionName() const {
121 if (const auto &plugin = getIbanBicData())
122 return plugin->requestData(bic(), eIBANBIC::DataType::bankNameByBic).toString();
123 return QString();
124 }
125
fullBic() const126 QString ibanBic::fullBic() const
127 {
128 if (m_bic.isNull())
129 if (const auto &plugin = getIbanBicData())
130 return plugin->requestData(m_iban, eIBANBIC::DataType::iban2Bic).toString();
131 return fullStoredBic();
132 }
133
bic() const134 QString ibanBic::bic() const
135 {
136 if (m_bic.isNull()) {
137 if (const auto &plugin = getIbanBicData()) {
138 auto bic = plugin->requestData(m_iban, eIBANBIC::DataType::iban2Bic).toString();
139 if (bic.length() == 11 && bic.endsWith(QLatin1String("XXX")))
140 return bic.left(8);
141 return bic;
142 }
143 }
144 return m_bic;
145 }
146
madeOfLettersAndNumbersOnly(const QString & string)147 inline bool madeOfLettersAndNumbersOnly(const QString& string)
148 {
149 const int length = string.length();
150 for (int i = 0; i < length; ++i) {
151 if (!string.at(i).isLetterOrNumber())
152 return false;
153 }
154 return true;
155 }
156
isValid() const157 bool ibanBic::isValid() const
158 {
159 Q_ASSERT(m_iban == ibanToElectronic(m_iban));
160
161 // Check BIC
162 const int bicLength = m_bic.length();
163 if (bicLength != 8 && bicLength != 11)
164 return false;
165
166 for (int i = 0; i < 6; ++i) {
167 if (!m_bic.at(i).isLetter())
168 return false;
169 }
170
171 for (int i = 6; i < bicLength; ++i) {
172 if (!m_bic.at(i).isLetterOrNumber())
173 return false;
174 }
175
176 // Check IBAN
177 const int ibanLength = m_iban.length();
178 if (ibanLength < 5 || ibanLength > 32)
179 return false;
180
181 if (!madeOfLettersAndNumbersOnly(m_iban))
182 return false;
183
184 /** @todo checksum */
185
186 return true;
187 }
188
isIbanValid() const189 bool ibanBic::isIbanValid() const
190 {
191 return isIbanValid(m_iban);
192 }
193
isIbanValid(const QString & iban)194 bool ibanBic::isIbanValid(const QString& iban)
195 {
196 return validateIbanChecksum(ibanToElectronic(iban));
197 }
198
ibanToElectronic(const QString & iban)199 QString ibanBic::ibanToElectronic(const QString& iban)
200 {
201 QString canonicalIban;
202 const int length = iban.length();
203 for (int i = 0; i < length; ++i) {
204 const QChar letter = iban.at(i);
205 if (letter.isLetterOrNumber())
206 canonicalIban.append(letter.toUpper());
207 }
208
209 return canonicalIban;
210 }
211
ibanToPaperformat(const QString & iban,const QString & separator)212 QString ibanBic::ibanToPaperformat(const QString& iban, const QString& separator)
213 {
214 QString paperformat;
215 const int length = iban.length();
216 int letterCounter = 0;
217 for (int i = 0; i < length; ++i) {
218 const QChar letter = iban.at(i);
219 if (letter.isLetterOrNumber()) {
220 ++letterCounter;
221 if (letterCounter == 5) {
222 paperformat.append(separator);
223 letterCounter = 1;
224 }
225 paperformat.append(letter);
226 }
227 }
228
229 if (paperformat.length() >= 2) {
230 paperformat[0] = paperformat[0].toUpper();
231 paperformat[1] = paperformat[1].toUpper();
232 }
233 return paperformat;
234 }
235
bban(const QString & iban)236 QString ibanBic::bban(const QString& iban)
237 {
238 return iban.mid(4);
239 }
240
validateIbanChecksum(const QString & iban)241 bool ibanBic::validateIbanChecksum(const QString& iban)
242 {
243 Q_ASSERT(iban == ibanToElectronic(iban));
244
245 // Reorder
246 QString reordered = iban.mid(4) + iban.left(4);
247
248 // Replace letters
249 for (int i = 0; i < reordered.length(); ++i) {
250 if (reordered.at(i).isLetter()) {
251 // Replace charactes A -> 10, ..., Z -> 35
252 reordered.replace(i, 1, QString::number(reordered.at(i).toLatin1() - 'A' + 10));
253 ++i; // the inserted number is always two characters long, jump beyond
254 }
255 }
256
257 // Calculations
258 try {
259 mpz_class number(reordered.toLatin1().constData(), 10);
260 return (number % 97 == 1);
261 } catch (std::invalid_argument&) {
262 // This can happen if the given iban contains incorrect data
263 }
264 return false;
265 }
266
getIbanBicData()267 KMyMoneyPlugin::DataPlugin *ibanBic::getIbanBicData()
268 {
269 return pPlugins.data.value(QString::fromLatin1("ibanbicdata"), nullptr);
270 }
271
ibanLengthByCountry(const QString & countryCode)272 int ibanBic::ibanLengthByCountry(const QString& countryCode)
273 {
274 if (const auto &plugin = getIbanBicData())
275 return (plugin->requestData(countryCode, eIBANBIC::DataType::bbanLength).toInt() + 4);
276 return int();
277 }
278
bicByIban(const QString & iban)279 QString ibanBic::bicByIban(const QString& iban)
280 {
281 if (const auto &plugin = getIbanBicData())
282 return (plugin->requestData(iban, eIBANBIC::DataType::iban2Bic).toString());
283 return QString();
284 }
285
localBankCodeByIban(const QString & iban)286 QString ibanBic::localBankCodeByIban(const QString& iban)
287 {
288 if (const auto &plugin = getIbanBicData())
289 return (plugin->requestData(iban, eIBANBIC::DataType::extractBankIdentifier).toString());
290 return QString();
291 }
292
institutionNameByBic(const QString & bic)293 QString ibanBic::institutionNameByBic(const QString& bic)
294 {
295 if (const auto &plugin = getIbanBicData())
296 return (plugin->requestData(bic, eIBANBIC::DataType::bankNameByBic).toString());
297 return QString();
298 }
299
bicToFullFormat(QString bic)300 QString ibanBic::bicToFullFormat(QString bic)
301 {
302 bic = bic.toUpper();
303 if (bic.length() == 8)
304 return (bic + QLatin1String("XXX"));
305 return bic;
306 }
307
isCanonicalBicAllocated(const QString & bic)308 ibanBic::bicAllocationStatus ibanBic::isCanonicalBicAllocated(const QString& bic)
309 {
310 Q_ASSERT(bic == bicToFullFormat(bic));
311
312 if (const auto &plugin = getIbanBicData()) {
313 auto status = static_cast<eIBANBIC::bicAllocationStatus>(plugin->requestData(bic, eIBANBIC::DataType::isBicAllocated).toInt());
314 switch (status) {
315 case eIBANBIC::bicAllocationStatus::bicAllocated: return bicAllocated;
316 case eIBANBIC::bicAllocationStatus::bicNotAllocated: return bicNotAllocated;
317 case eIBANBIC::bicAllocationStatus::bicAllocationUncertain: return bicAllocationUncertain;
318 }
319 }
320 return bicAllocationUncertain;
321 }
322
isBicAllocated(const QString & bic)323 ibanBic::bicAllocationStatus ibanBic::isBicAllocated(const QString& bic)
324 {
325 if (bic.length() != 8 && bic.length() != 11)
326 return bicNotAllocated;
327 return isCanonicalBicAllocated(bicToFullFormat(bic));
328 }
329
330
331 } // namespace payeeIdentifiers
332