def bank_info(bic): ''' Consult the free online SWIFT service to obtain the name and address of a bank. This call may take several seconds to complete, due to the number of requests to make. In total three HTTP requests are made per function call. In theory one request could be stripped, but the SWIFT terms of use prevent automated usage, so user like behavior is required. ''' def harvest(soup): retval = struct() for trsoup in soup('tr'): for stage, tdsoup in enumerate(trsoup('td')): if stage == 0: attr = tdsoup.contents[0].strip().replace(' ', '_') elif stage == 2: if tdsoup.contents: retval[attr] = tdsoup.contents[0].strip() else: retval[attr] = '' return retval # Get form agent = URLAgent() request = agent.open(SWIFTlink) soup = BeautifulSoup(request) # Parse request form. As this form is intertwined with a table, use the parent # as root to search for form elements. form = SoupForm(soup.find('form', {'id': 'frmFreeSearch1'}), parent=True) # Fill form fields form['selected_bic'] = bic # Get intermediate response response = agent.submit(form) # Parse response soup = BeautifulSoup(response) # Isolate the full 11 BIC - there may be more, but we only use the first bic_button = soup.find('a', {'class': 'bigbuttonblack'}) if not bic_button: return None, None # Overwrite the location with 'any' ('XXX') to narrow the results to one or less. # Assume this regexp will never fail... full_bic = bic_re.match(bic_button.get('href')).groups()[0][:8] + 'XXX' # Get the detail form form = SoupForm(soup.find('form', {'id': 'frmDetail'})) # Fill detail fields form['selected_bic11'] = full_bic # Get final response response = agent.submit(form) soup = BeautifulSoup(response) # Now parse the results tables = soup.find('div', {'id': 'Middle'}).findAll('table') if not tables: return None, None tablesoup = tables[2]('table') if not tablesoup: return None, None codes = harvest(tablesoup[0]) if not codes: return None, None bankinfo = struct( # Most banks use the first four chars of the BIC as an identifier for # their 'virtual bank' accross the world, containing all national # banks world wide using the same name. # The concatenation with the two character country code is for most # national branches sufficient as a unique identifier. code=full_bic[:6], bic=full_bic, name=codes.Institution_name, ) address = harvest(tablesoup[1]) # The address in the SWIFT database includes a postal code. # We need to split it into two fields... if not address.Zip_Code: if address.Location: iso, address.Zip_Code, address.Location = \ postalcode.split(address.Location, full_bic[4:6]) bankaddress = struct( street=address.Address.title(), city=address.Location.strip().title(), zip=address.Zip_Code, country=address.Country.title(), country_id=full_bic[4:6], ) if ' ' in bankaddress.street: bankaddress.street, bankaddress.street2 = [ x.strip() for x in bankaddress.street.split(' ', 1) ] else: bankaddress.street2 = '' return bankinfo, bankaddress
def parse_message(self): ''' Parse structured message parts into appropriate attributes ''' if self.transfer_type == 'ACC': # Accept Giro - structured message payment # First part of message is redundant information - strip it msg = self.message[self.message.index('navraagnr.'):] self.message = ' '.join(msg.split()) elif self.transfer_type == 'BEA': # Payment through payment terminal # Remote owner is part of message, while remote_owner is set # to the intermediate party, which we don't need. self.remote_owner = self.message[:23].rstrip() self.remote_owner_city = self.message[23:31].rstrip() self.message = self.message[31:] elif self.transfer_type == 'BTL': # International transfers. # Remote party is encoded in message, including bank costs parts = self.message.split(' ') last = False for part in parts: if part.startswith('bedrag. '): # The ordered transferred amount currency, amount = part.split('. ')[1].split() if self.remote_currency != currency.upper(): self.error_message = \ 'Remote currency in message differs from transaction.' else: self.local_amount = float(amount) elif part.startswith('koers. '): # The currency rate used self.exchange_rate = float(part.split('. ')[1]) elif part.startswith('transfer prov. '): # The provision taken by the bank # Note that the amount must be negated to get the right # direction currency, costs = part.split('. ')[1].split() self.provision_costs = -float(costs) self.provision_costs_currency = currency.upper() self.provision_costs_description = 'Transfer costs' elif part.startswith('aan. '): # The remote owner self.remote_owner = part.replace('aan. ', '').rstrip() last = True elif last: # Last parts are address lines address = part.rstrip() iso, pc, city = postalcode.split(address) if pc and city: self.remote_owner_postalcode = pc self.remote_owner_city = city.strip() self.remote_owner_country_code = iso else: self.remote_owner_address.append(address) elif self.transfer_type == 'DIV': # A diverse transfer. Message can be anything, but has some # structure ptr = self.message.find(self.reference) if ptr > 0: address = self.message[:ptr].rstrip().split(' ') length = len(address) if length >= 1: self.remote_owner = address[0] if length >= 2: self.remote_owner_address.append(address[1]) if length >= 3: self.remote_owner_city = address[2] self.message = self.message[ptr:].rstrip() if self.message.find('transactiedatum') >= 0: rest = self.message.split('transactiedatum') if rest[1].startswith('* '): self.execution_date = str2date(rest[1][2:], '%d-%m-%Y') else: self.execution_date = str2date(rest[1][2:], '%d %m %Y') self.message = rest[0].rstrip() elif self.transfer_type == 'IDB': # Payment by iDeal transaction # Remote owner can be part of message, while remote_owner can be # set to the intermediate party, which we don't need. parts = self.message.split(' ') # Second part: structured id, date & time subparts = parts[1].split() datestr = '-'.join(subparts[1:4]) timestr = ':'.join(subparts[4:]) parts[1] = ' '.join([subparts[0], datestr, timestr]) # Only replace reference when redundant if self.reference == parts[0]: if parts[2]: self.reference = ' '.join([parts[2], datestr, timestr]) else: self.reference += ' '.join([datestr, timestr]) # Optional fourth path contains remote owners name if len(parts) > 3 and parts[-1].find(self.remote_owner) < 0: self.remote_owner = parts[-1].rstrip() parts = parts[:-1] self.message = ' '.join(parts)
def parse_message(self): ''' Parse structured message parts into appropriate attributes ''' if self.transfer_type == 'ACC': # Accept Giro - structured message payment # First part of message is redundant information - strip it msg = self.message[self.message.index('navraagnr.'):] self.message = ' '.join(msg.split()) elif self.transfer_type == 'BEA': # Payment through payment terminal # Remote owner is part of message, while remote_owner is set # to the intermediate party, which we don't need. self.remote_owner = self.message[:23].rstrip() self.remote_owner_city = self.message[23:31].rstrip() self.message = self.message[31:] elif self.transfer_type == 'BTL': # International transfers. # Remote party is encoded in message, including bank costs parts = self.message.split(' ') last = False for part in parts: if part.startswith('bedrag. '): # The ordered transferred amount currency, amount = part.split('. ')[1].split() if self.remote_currency != currency.upper(): self.error_message = ( 'Remote currency in message differs from ' 'transaction.') else: self.local_amount = float(amount) elif part.startswith('koers. '): # The currency rate used self.exchange_rate = float(part.split('. ')[1]) elif part.startswith('transfer prov. '): # The provision taken by the bank # Note that the amount must be negated to get the right # direction currency, costs = part.split('. ')[1].split() self.provision_costs = -float(costs) self.provision_costs_currency = currency.upper() self.provision_costs_description = 'Transfer costs' elif part.startswith('aan. '): # The remote owner self.remote_owner = part.replace('aan. ', '').rstrip() last = True elif last: # Last parts are address lines address = part.rstrip() iso, pc, city = postalcode.split(address) if pc and city: self.remote_owner_postalcode = pc self.remote_owner_city = city.strip() self.remote_owner_country_code = iso else: self.remote_owner_address.append(address) elif self.transfer_type == 'DIV': # A diverse transfer. Message can be anything, but has some # structure ptr = self.message.find(self.reference) if ptr > 0: address = self.message[:ptr].rstrip().split(' ') length = len(address) if length >= 1: self.remote_owner = address[0] if length >= 2: self.remote_owner_address.append(address[1]) if length >= 3: self.remote_owner_city = address[2] self.message = self.message[ptr:].rstrip() if self.message.find('transactiedatum') >= 0: rest = self.message.split('transactiedatum') if rest[1].startswith('* '): self.execution_date = str2date(rest[1][2:], '%d-%m-%Y') else: self.execution_date = str2date(rest[1][2:], '%d %m %Y') self.message = rest[0].rstrip() elif self.transfer_type == 'IDB': # Payment by iDeal transaction # Remote owner can be part of message, while remote_owner can be # set to the intermediate party, which we don't need. parts = self.message.split(' ') # Second part: structured id, date & time subparts = parts[1].split() datestr = '-'.join(subparts[1:4]) timestr = ':'.join(subparts[4:]) parts[1] = ' '.join([subparts[0], datestr, timestr]) # Only replace reference when redundant if self.reference == parts[0]: if parts[2]: self.reference = ' '.join([parts[2], datestr, timestr]) else: self.reference += ' '.join([datestr, timestr]) # Optional fourth path contains remote owners name if len(parts) > 3 and parts[-1].find(self.remote_owner) < 0: self.remote_owner = parts[-1].rstrip() parts = parts[:-1] self.message = ' '.join(parts)
def bank_info(bic): ''' Consult the free online SWIFT service to obtain the name and address of a bank. This call may take several seconds to complete, due to the number of requests to make. In total three HTTP requests are made per function call. In theory one request could be stripped, but the SWIFT terms of use prevent automated usage, so user like behavior is required. ''' def harvest(soup): retval = struct() for trsoup in soup('tr'): for stage, tdsoup in enumerate(trsoup('td')): if stage == 0: attr = tdsoup.contents[0].strip().replace(' ','_') elif stage == 2: if tdsoup.contents: retval[attr] = tdsoup.contents[0].strip() else: retval[attr] = '' return retval # Get form agent = URLAgent() request = agent.open(SWIFTlink) soup = BeautifulSoup(request) # Parse request form. As this form is intertwined with a table, use the parent # as root to search for form elements. form = SoupForm(soup.find('form', {'id': 'frmFreeSearch1'}), parent=True) # Fill form fields form['selected_bic'] = bic # Get intermediate response response = agent.submit(form) # Parse response soup = BeautifulSoup(response) # Isolate the full 11 BIC - there may be more, but we only use the first bic_button = soup.find('a', {'class': 'bigbuttonblack'}) if not bic_button: return None, None # Overwrite the location with 'any' ('XXX') to narrow the results to one or less. # Assume this regexp will never fail... full_bic = bic_re.match(bic_button.get('href')).groups()[0][:8] + 'XXX' # Get the detail form form = SoupForm(soup.find('form', {'id': 'frmDetail'})) # Fill detail fields form['selected_bic11'] = full_bic # Get final response response = agent.submit(form) soup = BeautifulSoup(response) # Now parse the results tables = soup.find('div', {'id':'Middle'}).findAll('table') if not tables: return None, None tablesoup = tables[2]('table') if not tablesoup: return None, None codes = harvest(tablesoup[0]) if not codes: return None, None bankinfo = struct( # Most banks use the first four chars of the BIC as an identifier for # their 'virtual bank' accross the world, containing all national # banks world wide using the same name. # The concatenation with the two character country code is for most # national branches sufficient as a unique identifier. code = full_bic[:6], bic = full_bic, name = codes.Institution_name, ) address = harvest(tablesoup[1]) # The address in the SWIFT database includes a postal code. # We need to split it into two fields... if not address.Zip_Code: if address.Location: iso, address.Zip_Code, address.Location = \ postalcode.split(address.Location, full_bic[4:6]) bankaddress = struct( street = address.Address.title(), city = address.Location.strip().title(), zip = address.Zip_Code, country = address.Country.title(), country_id = full_bic[4:6], ) if ' ' in bankaddress.street: bankaddress.street, bankaddress.street2 = [ x.strip() for x in bankaddress.street.split(' ', 1) ] else: bankaddress.street2 = '' return bankinfo, bankaddress