1 module iban.constructor; 2 3 import std.typecons : Nullable, nullable; 4 import std.format : format; 5 import std.stdio; 6 7 import iban.validation; 8 import iban.structures; 9 import iban.ibans; 10 11 @safe: 12 13 Nullable!IBAN ibanFromString(string s) { 14 s = removeWhite(s); 15 Nullable!string specKey = extractCountryPrefix(s); 16 17 if(specKey.isNull()) { 18 return Nullable!(IBAN).init; 19 } 20 21 auto spec = specKey.get() in getIBANs(); 22 23 if(spec is null) { 24 return Nullable!(IBAN).init; 25 } 26 27 if(!isValidIBAN(s, *spec)) { 28 return Nullable!(IBAN).init; 29 } 30 31 if(s.length < 4) { 32 return Nullable!(IBAN).init; 33 } 34 35 IBAN ret; 36 ret.countryCode = s[0 .. 2]; 37 s = s[4 .. $]; 38 static foreach(it; 39 [ [ "accountCode", "account_code" ] 40 , [ "bankCode", "bank_code" ] 41 , [ "branchCode", "branch_code" ] 42 ]) 43 {{ 44 long[]* pos = it[1] in spec.positions; 45 if(pos !is null && (*pos).length > 1) { 46 long[] ppos = *pos; 47 ulong left = ppos[0]; 48 ulong right = ppos[1]; 49 if(left > s.length) { 50 return Nullable!(IBAN).init; 51 } 52 if(right > s.length) { 53 return Nullable!(IBAN).init; 54 } 55 __traits(getMember, ret, it[0]) = s[left .. right]; 56 } 57 }} 58 59 return nullable(ret); 60 } 61 62 unittest { 63 import iban.testdata; 64 65 foreach(it; valid) { 66 Nullable!IBAN t = ibanFromString(it); 67 assert(!t.isNull(), it); 68 } 69 } 70 71 unittest { 72 import iban.testdata; 73 74 foreach(it; invalid) { 75 Nullable!IBAN t = ibanFromString(it); 76 assert(t.isNull(), it); 77 } 78 } 79 80 Nullable!string buildIBANFromParts(string isoTwoDigitCountryCode, string bankCode 81 , string branchCode , string accountCode) 82 { 83 import std.algorithm.iteration : map, joiner; 84 import std.bigint; 85 import std.conv : to; 86 87 auto spec = isoTwoDigitCountryCode in getIBANs(); 88 89 if(spec is null) { 90 return Nullable!(string).init; 91 } 92 93 char[] tmp = new char[](spec.bbanLength); 94 tmp[] = '0'; 95 96 // The three parts of the IBAN according to the countries spec 97 static foreach(it; 98 [ [ "accountCode", "account_code" ] 99 , [ "bankCode", "bank_code" ] 100 , [ "branchCode", "branch_code" ] 101 ]) 102 {{ 103 long[]* pos = it[1] in spec.positions; 104 if(pos !is null && (*pos).length > 1) { 105 long[] ppos = *pos; 106 ulong left = ppos[0]; 107 ulong right = ppos[1]; 108 if(left > tmp.length) { 109 return Nullable!(string).init; 110 } 111 if(right > tmp.length) { 112 return Nullable!(string).init; 113 } 114 mixin(format("tmp[left .. right] = %s;\n", it[0])); 115 } 116 }} 117 118 // Computing the checksum 119 120 string bban = tmp 121 .map!(it => it >= 'A' 122 ? to!string(to!int(it - 'A' + 10)) 123 : to!string(it) 124 ) 125 .joiner("") 126 .to!string(); 127 128 string prefix = isoTwoDigitCountryCode 129 .map!(it => it >= 'A' 130 ? to!int(it - 'A' + 10) 131 : 0 132 ) 133 .map!(it => to!string(it)) 134 .joiner("") 135 .to!string(); 136 137 string cc = bban ~ prefix ~ "00"; 138 BigInt num = cc; 139 long mod = num % 97; 140 long chkSumTT = 98 - mod; 141 142 string ret = isoTwoDigitCountryCode ~ format("%02d", chkSumTT) 143 ~ tmp.to!string(); 144 145 return nullable(ret); 146 } 147 148 IBAN ibanFromDetails(string isoTwoDigitCountryCode, string bankCode 149 , string branchCode , string accountCode) 150 { 151 IBAN ret; 152 ret.countryCode = isoTwoDigitCountryCode; 153 ret.bankCode = bankCode; 154 ret.branchCode = branchCode; 155 ret.accountCode = accountCode; 156 157 Nullable!string data = buildIBANFromParts(isoTwoDigitCountryCode, bankCode 158 , branchCode, accountCode); 159 if(!data.isNull()) { 160 ret.iban = data.get(); 161 } 162 163 return ret; 164 } 165 166 unittest { 167 import iban.testdata; 168 169 foreach(it; valid) { 170 it = it.removeWhite(); 171 Nullable!IBAN t = ibanFromString(it); 172 assert(!t.isNull(), it); 173 IBAN tNN = t.get(); 174 auto r = buildIBANFromParts(tNN.countryCode, tNN.bankCode 175 , tNN.branchCode, tNN.accountCode); 176 assert(!r.isNull()); 177 auto rNN = r.get(); 178 if(rNN != it) { 179 writefln("\nexp: %s\ngot: %s", it, rNN); 180 } 181 } 182 }