Example #1
0
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
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
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