Home » Computing | Finance

Mashing up your bank and Wesabe

9. December 2008 by james 1 Comments

In my last post I described a means of using Grease Monkey and javascript to add extra functionality to web pages and in my previous example I demonstrated adding extra functionality to an online banking page.  At the end of that post I asked the question as to whether this was something that could be taken further and used to create a mash-up of your bank and a personal finance site.  In my case, I use Wesabe and whilst it is a great site to use I still find myself going to my traditional bank website for the standard balance/transaction stuff as it gives the most reliably up-to-date information.

If I've gone to the effort of giving friendly names to merchants in Wesabe, can't I get the benefits elsewhere as well?  Wesabe have provided an API for well over a year now and although I've had a dabble with it now and again I've found that until there's a means of updating information (aside from the undocumented statement upload function) I haven't really had that much use for it, until now.

Given the script from the original post, it's not a difficult jump to integrate functionality from Wesabe into our standard transaction statement:

 four-col    wesabeintegrated

Doesn't look hugely different but the original statement (on the left) label has been changed to the more readable name (on the right) I set in Wesabe and is now a link which allows me to open up the Wesabe merchant information in a separate window showing spending information etc.

It's only a small change and there is plenty more you could do with it; show the tags, the transaction impact on a spending target, how much you've spent at that merchant this month or anything else you can think of.  It's possible to do this with almost any online banking site, applying the same script (with about four line changes handling the different HTML) to the Lloyds TSB site gives a result as shown below:

Lloyds-before  Lloyds-after

I'm not giving a complete script for this (see the warning I placed in my previous post about running third party scripts in online banking sessions) but I will show some fragments and let you 'fill in the blanks' and adapt to your own bank's site.  The script is split into two main sections, the first part dealing with the interaction with Wesabe via their REST based API:

 

  1:   var WesabeAPI = {
  2: 
  3:     getRestResponse: function(url,functionref) {
  4:       var xmlDoc;
  5:       GM_xmlhttpRequest({
  6:         method: 'GET',
  7:         url: url,
  8:         headers: {
  9:             'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
 10:             'Accept': 'application/x-www-form-urlencoded',
 11:         },
 12:         onload: function(responseDetails) {
 13:           var parser = new DOMParser();
 14:           GM_log(responseDetails.responseText);
 15:           xmlDoc = parser.parseFromString(responseDetails.responseText, "application/xml");
 16:           functionref(xmlDoc);          
 17:         }
 18:       });
 19:     },
 20: 
 21:     // Take the last four digits of the account number and look up the wesabe id for the account.
 22:     // Cache the id after the first time
 23:     getWesabeDetails: function(accountId) {
 24:       var wesabeId = GM_getValue(accountId,"");
 25:       if (wesabeId.length != 0) WesabeAPI.getWesabeRawNames(wesabeId);
 26: 
 27:       WesabeAPI.getRestResponse('https://www.wesabe.com/accounts.xml',WesabeAPI.parseWesabeAccounts);      
 28:     },
 29: 
 30:     parseWesabeAccounts: function(xmlDoc) {
 31:       var accountNumbers = xmlDoc.getElementsByTagName("account-number");      
 32:       for (var i=0; i< accountNumbers.length; i++) {
 33:         if (accountNumbers[i].textContent == accountId) {
 34:           var accountNode = accountNumbers[i].parentNode;
 35:           GM_log(accountNode.localName);              
 36:           for (var j=0; j<accountNode.childNodes.length; j++)
 37:           {
 38:             if (accountNode.childNodes[j].localName=="id") {
 39:               GM_setValue(accountId,accountNode.childNodes[j].textContent);
 40:               WesabeAPI.getWesabeRawNames(accountNode.childNodes[j].textContent);                  
 41:             }
 42:           }
 43:         }
 44:       }
 45:     },
 46: 
 47:     getWesabeRawNames: function(wesabeAccountId) {
 48:       WesabeAPI.getRestResponse('https://www.wesabe.com/accounts/'+wesabeAccountId+'.xml?start_date=20080101&end_date=20081201',WesabeAPI.parseWesabeRawNames);
 49:     },
 50: 
 51:     parseWesabeRawNames: function(xmlDoc) {
 52:       var rawNameNodes = xmlDoc.getElementsByTagName("raw-name");
 53:       LloydsTSB.matchTransactions(rawNameNodes);              // <-- Alter this line to point to your bank specific update function
 54:     }
 55:   }
 56: 

 

You can use this section of the script pretty much as it stands - the only line you need to change is line 53 which makes a function call through to your bank specific code.  The entry point for this functionality is the getWesabeDetails function which takes the last four digits of your account number (which Wesabe uses as an identifier) and then finds out what Wesabe's internal identifier is for this account.  Once that's been retrieved it is cached to the script's sandboxed persistent storage and goes on to download the transactions for the time period we're interested in.  You can see that the time period is specified in the REST URL on line 48 of the snippet above, it's a horrible approach and would be the first thing to clean up in your own version of the script (along with removing some of the debug logging 'GM_log' statements).

Line 53 then calls in to your bank specific code, in this case that of Lloyds TSB which is shown below:

  1:   var LloydsTSB = {
  2:     updateItem: function(descriptionElement, transaction) {
  3:       // check for merchant info
  4:       var merchantNode = LloydsTSB.findChildByName(transaction,"merchant");
  5:       if (merchantNode) {
  6:         var idNode = LloydsTSB.findChildByName(merchantNode,"id");
  7:         var nameNode = LloydsTSB.findChildByName(merchantNode,"name");
  8:         descriptionElement.innerHTML = "<a href=\"https://www.wesabe.com/transactions/merchant/"+idNode.textContent+"\" target=\"_blank\">"+nameNode.textContent+"</a>";      
  9:       }
 10:     },
 11: 
 12:     findChildByName: function(parentNode, localName) {
 13:       for (var i=0; i<parentNode.childNodes.length; i++)
 14:       {
 15:         if (parentNode.childNodes[i].localName==localName) {
 16:           return parentNode.childNodes[i];        
 17:         }
 18:       }
 19:     },
 20: 
 21:     getAccountIdentifier: function() {
 22:       var cardNumberElement = document.evaluate("//TD[@class='accountDetailsTableContainer5']//DIV[2]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
 23:       return cardNumberElement.innerHTML.substring(4);  
 24:     },
 25: 
 26:     matchTransactions: function(wesabeRawNames) {
 27:       var rowElements = document.evaluate("//DIV[@id='table']//TBODY/TR/TD[4]", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
 28:       for (var i = 0; i < rowElements.snapshotLength ; i++) {        
 29:         var descripElement = rowElements.snapshotItem(i);
 30:         if (!descripElement) continue;        
 31:         // is there a matching transaction
 32:         var text = descripElement.innerHTML;
 33:         text = text.replace('<br>',' . ');        
 34:         text=text.replace(/^\s*/, '').replace(/\s*$/, ''); 
 35: 
 36:         while (text.indexOf("  ")!=-1) {
 37:           text=text.replace("  "," ");            
 38:         }
 39: 
 40:         for (var j = 0; j<wesabeRawNames.length; j++ ) {
 41:           if (text == wesabeRawNames[j].textContent) {
 42:             LloydsTSB.updateItem(descripElement, wesabeRawNames[j].parentNode);
 43:             break;              
 44:           }            
 45:         }        
 46:       }
 47:     }
 48:   }

 

Ignoring the helper methods in there, the two functions you will need to write are getAccountIdentifier to get that four digit account suffix to pass to Wesabe for lookup and the matchTransactions function which will match the Wesabe raw transaction names to those on your statement.  Once the transactions have been matched you can insert whatever you want to provide the extra functionality, in the snippet above it uses the friendly name and inserts a link.

That's pretty much it, you just need to add the two lines to call getAccountIdentifier and pass the result to the WesabeAPI.getWesabeDetails function.

You'll notice that Wesabe authentication is not mentioned anywhere in the code snippets, I purposefully didn't include it and when the script runs it will prompt you for your Wesabe credentials (only does this once per browser session).  It gives you the option to cancel it and not augment your statement if you don't want to.  Another option would be to persist your Wesabe credentials in Grease Monkey settings so that it never asks you aside from the first time you run the script.  You'd need to weight up the pros and cons to that approach yourself.

So there you have it, an augmented statement with extra details and no-one else had to do anything to achieve it.  It's by no means an ideal solution and using scripts like this is something to do with your eyes very much open but the fact that the functionality 'appears' within a few seconds of you opening your statement is quite neat and can make the statement a little more personal in appearance.  One way to take this forwards might be to add the functionality to perform a statement upload into Wesabe directly from your bank's site.

It should go without saying but please do not send me any bank statements or any other private or personal information for help with writing a script, that said if you want to try something similar yourself and run into trouble then feel free to leave a comment.

 

Technorati Tags: ,,

Currently rated 4.5 by 4 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments