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