UNPKG

banker

Version:

Downloads transactions and balances from online banking websites using Zombie.js.

273 lines (229 loc) 9.03 kB
var BankSession = require('./base'), csv = require('csv'), moment = require('moment'), util = require('util'), utils = require('../utils'); function PaypalSession(config) { var self = this; BankSession.call(self, config); self.loginUrl = 'https://www.paypal.com/us/home'; self.accountUrl = 'https://www.paypal.com/us/cgi-bin/webscr?cmd=_account'; self.transactionsUrl = 'https://history.paypal.com/us/cgi-bin/webscr?cmd=_history'; self.logoutUrl = 'https://www.paypal.com/us/cgi-bin/webscr?cmd=_logout'; self.waitOptions = { duration : 10000 }; self.waitOptions2 = { duration : 40000 }; } util.inherits(PaypalSession, BankSession); PaypalSession.prototype.info = function() { return { description : 'PayPal (https://www.paypal.com/)', configItems : { bankName : '"paypal"', email : 'PayPal email address (username)', password : 'PayPal password', welcomeText : 'The big welcome text after you log in. Either ' + '"Welcome, Firstname Lastname" or "Hi again, Firstname!"' }, accountItems : { description : 'Account description (free-form, not used)' } }; }; PaypalSession.prototype.login = function(cb) { var self = this; if (self.loggedIn) { self.log('already logged in'); cb(null); return; } if (!self.config.welcomeText) { cb(new Error( 'Fill in the welcomeText config property for this account.')); } self.log('visiting login page'); self.browser.visit(self.loginUrl, function() { if (!self.pageLoaded('login', cb)) return; self.log('sending username and password'); try { self.browser.fill('#login_email' , self.config.email); self.browser.fill('#login_password', self.config.password); } catch (err) { cb(err); return; } self.browser.click('input[name=submit]'); self.browser.wait(self.waitOptions2, function() { if (!self.pageLoaded('afterLogin0', cb)) return; try { var errorText = self.browser.text('div.alert.alert-warning'); if (errorText) { cb(new Error( 'Error from PayPal: ' + errorText)); return; } } catch (err) { } self.log('visiting account page'); self.browser.visit(self.accountUrl, function() { if (!self.pageLoaded('account', cb)) return; var welcomeText = null; try { welcomeText = self.browser.text('#headline > h2'); } catch (err) { } if (!welcomeText) { try { welcomeText = self.browser.text('p.nemo_welcomeMessageHeader'); self._usingNewPaypal = true; } catch (err) { } } if (welcomeText != self.config.welcomeText) { cb(new Error( 'Expected welcome text to equal "' + self.config.welcomeText + '" but found "' + welcomeText + '".')); return; } self._loggedIn(cb); }); }); }); }; PaypalSession.prototype._loggedIn = function(cb) { var self = this; self.loggedIn = true; self.log('logged in'); cb(null); }; PaypalSession.prototype.getTransactions = function(account, cb) { var self = this; var data = { transactions : [], balances : {} }; self.log('visiting account page'); self.browser.visit(self.accountUrl, function() { if (!self.pageLoaded('account', cb)) return; try { if (self._usingNewPaypal) { data.balances.actual = utils.decimal( self.browser.text( '.balanceModule .balanceNumeral .large')); } else { data.balances.actual = utils.decimal( self.browser.text( 'li.balance span.balance strong').split(' ')[0]); } } catch (err) { cb(err); return; } self.log('visiting transaction history page'); self.browser.visit(self.transactionsUrl, function() { if (!self.pageLoaded('transactions', cb)) return; if (account.minDate || account.maxDate) { var minDateSel = '#from_date', maxDateSel = '#to_date'; if (account.minDate) { self.browser.fill(minDateSel, account.minDate); } if (account.maxDate) { self.browser.fill(maxDateSel, account.maxDate); } var minDate = self.browser.query(minDateSel).value, maxDate = self.browser.query(maxDateSel).value; self.log('getting data from ' + minDate + ' to ' + maxDate); self.browser.click('#show'); self.browser.wait(self.waitOptions, function() { if (!self.pageLoaded('accountExpanded', cb)) return; self._scrapeTransactions(data, cb); }); } else { self._scrapeTransactions(data, cb); } }); }); }; PaypalSession.prototype._scrapeTransactions = function(data, cb) { var self = this; self.log('downloading transactions as CSV'); var csvButton = null; try { csvButton = self.browser.querySelector('input[name=download_file_submit]'); } catch (err) { } if (!csvButton) { cb(new Error( 'Could not find CSV download button.')); return; } self.browser.click(csvButton); self.browser.wait(self.waitOptions2, function() { if (!self.pageLoaded('csv', cb)) return; var csvData = self.browser.response.body; // Change headers from "Field1, Field2, " to "Field1,Field2" csvData = csvData.split('\n'); csvData[0] = csvData[0] .replace(/[,\s]+$/, '') .split(',') .map(function(fieldName) { return fieldName.trim(); }) .join(','); csvData = csvData.join('\n'); csv.parse(csvData, { columns : true }, function(err, rows) { data.balances.fromList = utils.decimal(rows[0]['Balance']); rows.forEach(function(row) { switch (row['Type']) { case 'Invoice Sent': case 'Update to eCheck Received': case 'Cancelled Fee': // These are not real transactions; ignore them break; default: if (row['Status'] != 'Uncleared' && row['Status'] != 'Pending') { var transaction = { date : moment(row['Date'], 'MM/DD/YYYY'), description : row['Type'] + ' (' + row['Name'] + ')', memo : row['Status'], sourceId : row['Transaction ID'], // Non-standard fields follow fromName : row['Name'], grossAmount : utils.decimal(row['Gross']), feeAmount : utils.decimal(row['Fee']), netAmount : utils.decimal(row['Net']) }; transaction.amount = transaction.netAmount; var from = row['From Email Address'].trim(); if (from) { transaction.fromEmail = from; // non-standard field transaction.memo += ' from ' + from; } data.transactions.push(transaction); } break; } }); }); cb(null, data); }); }; PaypalSession.prototype.logout = function(cb) { var self = this; if (!cb) cb = function() { }; if (!self.loggedIn) { self.log('may not be logged in'); } self.log('visiting logout page'); self.browser.errors = []; self.browser.visit(self.logoutUrl, function() { if (!self.pageLoaded('logout', cb)) return; var err = self.browser.error; if (!err || err.message == "Cannot call method 'indexOf' of null") { self.loggedIn = false; self.log('logged out'); err = null; } cb(err); }); }; module.exports = PaypalSession;