• Hey there! Welcome to TFC! View fewer ads on the website just by signing up on TF Community.

App to track all credit card expenses [Since Activation] in one place

Here's the script that I use. Create a Google sheet and update the sheetUrl in the first line and setup trigger to run this every night between 12 - 1am.

JavaScript:
var sheetUrl = "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit#gid=0";
var momentScript = "https://cdn.jsdelivr.net/npm/moment/moment.min.js";

var bankConfig = {
  "HDFC": {
    "email": "alerts@hdfcbank.net",
    "subject": "HDFC Bank Credit Card",
    "regex": /Card .+?(\d{4}) for ([A-Za-z]+)\.? ([\d.]+) at (.+?) on (\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2})\.? Authorization code:- (\d+)/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5,
      "authcode": 6
    },
    "sheetName": "HDFC"
  },
  "Axis": {
    "email": "alerts@axisbank.com",
    "subject": "Transaction alert on Axis Bank Credit Card",
    "regex": /no. XX(\d{4}) for (INR|USD) ([\d.]+) at (.+?) on (\d{2}-\d{2}-(?:\d{2}|\d{4}) \d{2}:\d{2}:\d{2})/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Axis"
  },
  "Kotak": {
    "email": "creditcardalerts@kotak.com",
    "subject": "Kotak Bank Credit Card Transaction Alert",
    "regex": /A Transaction of (INR|USD) ([\d.]+) has been done on your Kotak Bank Credit Card No.xx(\d{4}) at (.+?) on (\d{2}-\w+-\d{4})/,
    "dateFormat": 'DD-MMM-YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Kotak"
  },
  "ICICI": {
    "email": "credit_cards@icicibank.com",
    "subject": "Transaction alert for your ICICI Bank Credit Card",
    "regex": /ICICI Bank Credit Card XX(\d{4}) has been used for a transaction of (INR|USD) ([\d.]+) on (\w{3}\s\d{2},\s\d{4} at \d{2}:\d{2}:\d{2}). Info: ([^.]+)/,
    "dateFormat": 'MMM DD, YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 6,
      "date": 4
    },
    "sheetName": "ICICI"
  },
  "SBI": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "Transaction Alert from CASHBACK SBI Card",
    "regex": /(Rs.|USD)([\d.,]+) spent on your SBI Credit Card ending (\d{4}) at ([^.]+) on (\d{2}\/\d{2}\/\d{2})/,
    "dateFormat": 'DD/MM/YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "SBI"
  },
  "SBI Recurring": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "SBI Credit Card",
    "regex": /(Rs.|USD)([\d.,]+) at ([^.]+) against (?:.+?) SBI Credit Card ending (\d{4}) on (\d{2}-\d{2}-\d{2})/,
    "dateFormat": 'DD-MM-YYYY',
    "captureGroups": {
      "card": 4,
      "currency": 1,
      "amount": 2,
      "merchant": 3,
      "date": 5
    },
    "sheetName": "SBI"
  }
};

function processTransactionEmails() {
  for (var card in bankConfig) {
    var messages = getRelevantMessages(card);
    var records = parseMessageData(messages, card);
    saveDataToSheet(records, card);
  }
  return true;
}

function getRelevantMessages(card) {
  var config = bankConfig[card];

  // Ensure the Moment.js library is loaded
  loadMomentJS();

  var startOfDay = moment().startOf('day').subtract(1, 'day');
  var startOfDayTimestamp = startOfDay.unix(); // Get the timestamp in seconds
  //startOfDayTimestamp = 1711929600;

  var query = 'from:' + config.email + ' AND subject:"' + config.subject + '" AND after:' + startOfDayTimestamp;

  Logger.log("Searching for messages using: " + query);

  var messages = [];
  var start = 0;
  var maxResults = 100;

  while (true) {
    var threads = GmailApp.search(query, start, maxResults);
    if (threads.length == 0) {
      break;
    }

    for (var i = 0; i < threads.length; i++) {
      messages = messages.concat(threads[i].getMessages());
    }

    start += maxResults;
  }

  return messages;
}

// Dynamically load the script using the URL
function loadMomentJS() {
  var response = UrlFetchApp.fetch(momentScript); // Fetch the script from the CDN
  eval(response.getContentText());  // Execute the script in the Apps Script environment
}

function parseMessageData(messages, card) {
  var config = bankConfig[card];
  var records = [];
  if (!messages || messages.length === 0) {
    Logger.log("No messages found.");
    return records;
  }

  for (var m = 0; m < messages.length; m++) {
    var text = messages[m].getPlainBody();
    text = text.replace(/\s+/g, " ").trim();

    var matches = text.match(config.regex);
    Logger.log({ text, matches });

    if (!matches) {
      continue;
    }

    var rec = {};
    for (var key in config.captureGroups) {
      var index = config.captureGroups[key];
      rec[key] = index !== null ? matches[index] : null;
    }

    // Format input date to consistent format.
    if (rec.date) {
      rec.date = moment(rec.date, config.dateFormat).format("MM/DD/YYYY HH:mm:ss")
    }

    rec.multiplier = 1;

    if (rec.merchant && rec.merchant.toLowerCase().includes("gyftr")) {
      rec.multiplier = 5;
    } else if (rec.merchant && rec.merchant.includes("smartbuy")) {
      if (rec.merchant.includes("jockey") || rec.merchant.includes("hotel") || rec.merchant.includes("pharmeasy")) {
        rec.multiplier = 10;
      }
    }

    Logger.log({ rec });
    records.push(rec);
  }
  return records;
}

function saveDataToSheet(records, card) {
  var config = bankConfig[card];
  var spreadsheet = SpreadsheetApp.openByUrl(sheetUrl);
  var sheet = spreadsheet.getSheetByName(config.sheetName);
  Logger.log("Saving " + records.length + " records");
  var lastRow = sheet.getLastRow();

  for (var r = 0; r < records.length; r++) {
    var rowData = [records[r].date, "'" + records[r].card, records[r].merchant, records[r].currency, records[r].amount];
    if (records[r].authcode) {
      rowData.push(records[r].authcode);
    }
    if (card === "HDFC") {
      rowData.push(records[r].multiplier);
    }

    sheet.appendRow(rowData);
    lastRow = sheet.getLastRow();

    sheet.getRange(lastRow, 5).setNumberFormat("0.00");
    if (records[r].authcode) {
      var authCodeCell = sheet.getRange(lastRow, 6);
      authCodeCell.setNumberFormat("@");
      authCodeCell.setValue("'" + records[r].authcode);
    }

    if (card === "HDFC") {
      var formula = "=ROUNDDOWN(E" + lastRow + "/150,0)*5*G" + lastRow;
      sheet.getRange(lastRow, 8).setFormula(formula);
    }
  }
}
 
Here's the script that I use. Create a Google sheet and update the sheetUrl in the first line and setup trigger to run this every night between 12 - 1am.

JavaScript:
var sheetUrl = "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit#gid=0";
var momentScript = "https://cdn.jsdelivr.net/npm/moment/moment.min.js";

var bankConfig = {
  "HDFC": {
    "email": "alerts@hdfcbank.net",
    "subject": "HDFC Bank Credit Card",
    "regex": /Card .+?(\d{4}) for ([A-Za-z]+)\.? ([\d.]+) at (.+?) on (\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2})\.? Authorization code:- (\d+)/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5,
      "authcode": 6
    },
    "sheetName": "HDFC"
  },
  "Axis": {
    "email": "alerts@axisbank.com",
    "subject": "Transaction alert on Axis Bank Credit Card",
    "regex": /no. XX(\d{4}) for (INR|USD) ([\d.]+) at (.+?) on (\d{2}-\d{2}-(?:\d{2}|\d{4}) \d{2}:\d{2}:\d{2})/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Axis"
  },
  "Kotak": {
    "email": "creditcardalerts@kotak.com",
    "subject": "Kotak Bank Credit Card Transaction Alert",
    "regex": /A Transaction of (INR|USD) ([\d.]+) has been done on your Kotak Bank Credit Card No.xx(\d{4}) at (.+?) on (\d{2}-\w+-\d{4})/,
    "dateFormat": 'DD-MMM-YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Kotak"
  },
  "ICICI": {
    "email": "credit_cards@icicibank.com",
    "subject": "Transaction alert for your ICICI Bank Credit Card",
    "regex": /ICICI Bank Credit Card XX(\d{4}) has been used for a transaction of (INR|USD) ([\d.]+) on (\w{3}\s\d{2},\s\d{4} at \d{2}:\d{2}:\d{2}). Info: ([^.]+)/,
    "dateFormat": 'MMM DD, YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 6,
      "date": 4
    },
    "sheetName": "ICICI"
  },
  "SBI": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "Transaction Alert from CASHBACK SBI Card",
    "regex": /(Rs.|USD)([\d.,]+) spent on your SBI Credit Card ending (\d{4}) at ([^.]+) on (\d{2}\/\d{2}\/\d{2})/,
    "dateFormat": 'DD/MM/YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "SBI"
  },
  "SBI Recurring": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "SBI Credit Card",
    "regex": /(Rs.|USD)([\d.,]+) at ([^.]+) against (?:.+?) SBI Credit Card ending (\d{4}) on (\d{2}-\d{2}-\d{2})/,
    "dateFormat": 'DD-MM-YYYY',
    "captureGroups": {
      "card": 4,
      "currency": 1,
      "amount": 2,
      "merchant": 3,
      "date": 5
    },
    "sheetName": "SBI"
  }
};

function processTransactionEmails() {
  for (var card in bankConfig) {
    var messages = getRelevantMessages(card);
    var records = parseMessageData(messages, card);
    saveDataToSheet(records, card);
  }
  return true;
}

function getRelevantMessages(card) {
  var config = bankConfig[card];

  // Ensure the Moment.js library is loaded
  loadMomentJS();

  var startOfDay = moment().startOf('day').subtract(1, 'day');
  var startOfDayTimestamp = startOfDay.unix(); // Get the timestamp in seconds
  //startOfDayTimestamp = 1711929600;

  var query = 'from:' + config.email + ' AND subject:"' + config.subject + '" AND after:' + startOfDayTimestamp;

  Logger.log("Searching for messages using: " + query);

  var messages = [];
  var start = 0;
  var maxResults = 100;

  while (true) {
    var threads = GmailApp.search(query, start, maxResults);
    if (threads.length == 0) {
      break;
    }

    for (var i = 0; i < threads.length; i++) {
      messages = messages.concat(threads[i].getMessages());
    }

    start += maxResults;
  }

  return messages;
}

// Dynamically load the script using the URL
function loadMomentJS() {
  var response = UrlFetchApp.fetch(momentScript); // Fetch the script from the CDN
  eval(response.getContentText());  // Execute the script in the Apps Script environment
}

function parseMessageData(messages, card) {
  var config = bankConfig[card];
  var records = [];
  if (!messages || messages.length === 0) {
    Logger.log("No messages found.");
    return records;
  }

  for (var m = 0; m < messages.length; m++) {
    var text = messages[m].getPlainBody();
    text = text.replace(/\s+/g, " ").trim();

    var matches = text.match(config.regex);
    Logger.log({ text, matches });

    if (!matches) {
      continue;
    }

    var rec = {};
    for (var key in config.captureGroups) {
      var index = config.captureGroups[key];
      rec[key] = index !== null ? matches[index] : null;
    }

    // Format input date to consistent format.
    if (rec.date) {
      rec.date = moment(rec.date, config.dateFormat).format("MM/DD/YYYY HH:mm:ss")
    }

    rec.multiplier = 1;

    if (rec.merchant && rec.merchant.toLowerCase().includes("gyftr")) {
      rec.multiplier = 5;
    } else if (rec.merchant && rec.merchant.includes("smartbuy")) {
      if (rec.merchant.includes("jockey") || rec.merchant.includes("hotel") || rec.merchant.includes("pharmeasy")) {
        rec.multiplier = 10;
      }
    }

    Logger.log({ rec });
    records.push(rec);
  }
  return records;
}

function saveDataToSheet(records, card) {
  var config = bankConfig[card];
  var spreadsheet = SpreadsheetApp.openByUrl(sheetUrl);
  var sheet = spreadsheet.getSheetByName(config.sheetName);
  Logger.log("Saving " + records.length + " records");
  var lastRow = sheet.getLastRow();

  for (var r = 0; r < records.length; r++) {
    var rowData = [records[r].date, "'" + records[r].card, records[r].merchant, records[r].currency, records[r].amount];
    if (records[r].authcode) {
      rowData.push(records[r].authcode);
    }
    if (card === "HDFC") {
      rowData.push(records[r].multiplier);
    }

    sheet.appendRow(rowData);
    lastRow = sheet.getLastRow();

    sheet.getRange(lastRow, 5).setNumberFormat("0.00");
    if (records[r].authcode) {
      var authCodeCell = sheet.getRange(lastRow, 6);
      authCodeCell.setNumberFormat("@");
      authCodeCell.setValue("'" + records[r].authcode);
    }

    if (card === "HDFC") {
      var formula = "=ROUNDDOWN(E" + lastRow + "/150,0)*5*G" + lastRow;
      sheet.getRange(lastRow, 8).setFormula(formula);
    }
  }
}
Legend 🙏🏻 thank you
 
I proposed this idea to Cred during an interview with them around 3 yrs back. the interviewer said, I don't understand what Cred vision is and rejected me! Cred already read all statements, they just needed to build an analytical layer over this!
there are multiple use cases, like how much one spends in a Year on Swiggy or Amazon!
 
Here's the script that I use. Create a Google sheet and update the sheetUrl in the first line and setup trigger to run this every night between 12 - 1am.

JavaScript:
var sheetUrl = "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit#gid=0";
var momentScript = "https://cdn.jsdelivr.net/npm/moment/moment.min.js";

var bankConfig = {
  "HDFC": {
    "email": "alerts@hdfcbank.net",
    "subject": "HDFC Bank Credit Card",
    "regex": /Card .+?(\d{4}) for ([A-Za-z]+)\.? ([\d.]+) at (.+?) on (\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2})\.? Authorization code:- (\d+)/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5,
      "authcode": 6
    },
    "sheetName": "HDFC"
  },
  "Axis": {
    "email": "alerts@axisbank.com",
    "subject": "Transaction alert on Axis Bank Credit Card",
    "regex": /no. XX(\d{4}) for (INR|USD) ([\d.]+) at (.+?) on (\d{2}-\d{2}-(?:\d{2}|\d{4}) \d{2}:\d{2}:\d{2})/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Axis"
  },
  "Kotak": {
    "email": "creditcardalerts@kotak.com",
    "subject": "Kotak Bank Credit Card Transaction Alert",
    "regex": /A Transaction of (INR|USD) ([\d.]+) has been done on your Kotak Bank Credit Card No.xx(\d{4}) at (.+?) on (\d{2}-\w+-\d{4})/,
    "dateFormat": 'DD-MMM-YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Kotak"
  },
  "ICICI": {
    "email": "credit_cards@icicibank.com",
    "subject": "Transaction alert for your ICICI Bank Credit Card",
    "regex": /ICICI Bank Credit Card XX(\d{4}) has been used for a transaction of (INR|USD) ([\d.]+) on (\w{3}\s\d{2},\s\d{4} at \d{2}:\d{2}:\d{2}). Info: ([^.]+)/,
    "dateFormat": 'MMM DD, YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 6,
      "date": 4
    },
    "sheetName": "ICICI"
  },
  "SBI": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "Transaction Alert from CASHBACK SBI Card",
    "regex": /(Rs.|USD)([\d.,]+) spent on your SBI Credit Card ending (\d{4}) at ([^.]+) on (\d{2}\/\d{2}\/\d{2})/,
    "dateFormat": 'DD/MM/YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "SBI"
  },
  "SBI Recurring": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "SBI Credit Card",
    "regex": /(Rs.|USD)([\d.,]+) at ([^.]+) against (?:.+?) SBI Credit Card ending (\d{4}) on (\d{2}-\d{2}-\d{2})/,
    "dateFormat": 'DD-MM-YYYY',
    "captureGroups": {
      "card": 4,
      "currency": 1,
      "amount": 2,
      "merchant": 3,
      "date": 5
    },
    "sheetName": "SBI"
  }
};

function processTransactionEmails() {
  for (var card in bankConfig) {
    var messages = getRelevantMessages(card);
    var records = parseMessageData(messages, card);
    saveDataToSheet(records, card);
  }
  return true;
}

function getRelevantMessages(card) {
  var config = bankConfig[card];

  // Ensure the Moment.js library is loaded
  loadMomentJS();

  var startOfDay = moment().startOf('day').subtract(1, 'day');
  var startOfDayTimestamp = startOfDay.unix(); // Get the timestamp in seconds
  //startOfDayTimestamp = 1711929600;

  var query = 'from:' + config.email + ' AND subject:"' + config.subject + '" AND after:' + startOfDayTimestamp;

  Logger.log("Searching for messages using: " + query);

  var messages = [];
  var start = 0;
  var maxResults = 100;

  while (true) {
    var threads = GmailApp.search(query, start, maxResults);
    if (threads.length == 0) {
      break;
    }

    for (var i = 0; i < threads.length; i++) {
      messages = messages.concat(threads[i].getMessages());
    }

    start += maxResults;
  }

  return messages;
}

// Dynamically load the script using the URL
function loadMomentJS() {
  var response = UrlFetchApp.fetch(momentScript); // Fetch the script from the CDN
  eval(response.getContentText());  // Execute the script in the Apps Script environment
}

function parseMessageData(messages, card) {
  var config = bankConfig[card];
  var records = [];
  if (!messages || messages.length === 0) {
    Logger.log("No messages found.");
    return records;
  }

  for (var m = 0; m < messages.length; m++) {
    var text = messages[m].getPlainBody();
    text = text.replace(/\s+/g, " ").trim();

    var matches = text.match(config.regex);
    Logger.log({ text, matches });

    if (!matches) {
      continue;
    }

    var rec = {};
    for (var key in config.captureGroups) {
      var index = config.captureGroups[key];
      rec[key] = index !== null ? matches[index] : null;
    }

    // Format input date to consistent format.
    if (rec.date) {
      rec.date = moment(rec.date, config.dateFormat).format("MM/DD/YYYY HH:mm:ss")
    }

    rec.multiplier = 1;

    if (rec.merchant && rec.merchant.toLowerCase().includes("gyftr")) {
      rec.multiplier = 5;
    } else if (rec.merchant && rec.merchant.includes("smartbuy")) {
      if (rec.merchant.includes("jockey") || rec.merchant.includes("hotel") || rec.merchant.includes("pharmeasy")) {
        rec.multiplier = 10;
      }
    }

    Logger.log({ rec });
    records.push(rec);
  }
  return records;
}

function saveDataToSheet(records, card) {
  var config = bankConfig[card];
  var spreadsheet = SpreadsheetApp.openByUrl(sheetUrl);
  var sheet = spreadsheet.getSheetByName(config.sheetName);
  Logger.log("Saving " + records.length + " records");
  var lastRow = sheet.getLastRow();

  for (var r = 0; r < records.length; r++) {
    var rowData = [records[r].date, "'" + records[r].card, records[r].merchant, records[r].currency, records[r].amount];
    if (records[r].authcode) {
      rowData.push(records[r].authcode);
    }
    if (card === "HDFC") {
      rowData.push(records[r].multiplier);
    }

    sheet.appendRow(rowData);
    lastRow = sheet.getLastRow();

    sheet.getRange(lastRow, 5).setNumberFormat("0.00");
    if (records[r].authcode) {
      var authCodeCell = sheet.getRange(lastRow, 6);
      authCodeCell.setNumberFormat("@");
      authCodeCell.setValue("'" + records[r].authcode);
    }

    if (card === "HDFC") {
      var formula = "=ROUNDDOWN(E" + lastRow + "/150,0)*5*G" + lastRow;
      sheet.getRange(lastRow, 8).setFormula(formula);
    }
  }
}
@HumorSimpson . .
 
Track Cards ->

This is only a UAT version, which has the initial features. For details, please read my first post here.

I have added a test user with some dummy data to get an idea of how things work on the website.

You can also create your own account to play around.

Please consider the following facts while using Track Cards -
  • Currently Track Cards is running in free server which has limited facilities. Henceforth, first time login/register button clicks may take around 1-2 minutes to load. You should receive a confirmation at the right-bottom of the page (toast) either in Green or Red depending on success and error responses respectively, notifying that you are good to use the app now.
  • Remember your password as its a one side encryption, it is impossible to recover. I'll develop password reset functionality soon. Dont register with invalid email, as in sometime you'll loose your account once I develop email verification feature
  • This is only for demo purposes. Refrain from uploading all your data, as this data will not be migrated. Though you can upload some data to see if things are working for you.
  • First step is to Add a Card from "Cards" tab and then you can Upload your statement from "Transactions" tab. Though I intend to avoid saving any kind of personal info like card number, cvv, expiry, name etc, at this point I'd suggest uploading dummy data instead of your actual statement
  • Until a single card is added, you won't be able to use any of the header tabs except "Cards".
  • As of now Track Cards support only limited credit cards as shared below in the list. The logic to convert monthly statement pdf's raw data into Transaction and Statement records differs for each credit card. So I was able to develop the algorithm only for the cards I am currently holding. It requires manual effort to read and observe the pattern from at least 4 to 5 statement pdfs for onboarding a new credit card. Also the current benefits (basically rewards structure) have to be implemented in the business logic for properly tracking the card usage.
  • Copy only the transactions from your statement and paste into a .txt file to upload. One month's statement data at a time should be uploaded. Please check the View Instruction before uploading. Uploading can be done from "Transactions -> Upload Statement" option

  • Under the "Home" tab, hovering mouse over a particular thumbnail image of a credit card will show its benefits in a glance
  • I have tested all features from Track Cards and made sure that they are working perfect. Still there may exists some bugs considering its only in very initial phase. Do let me know here if you find any issue.
  • Regarding enhancement to support other type of credit cards, I'll need a few statement pdfs to develop the algorithm for converting raw statement data into Transactions & Statements per credit card. But this will be the least priority and will be done only if I decide to go live in production. As I alone cant do it and I'll need some of you to share your statement pdfs for building the business logic for different cards.

Note:
As some of you were asking, I haven't thought of any paid subscription plan yet. My initial intention was to use the app only for my personal purposes and started it as a hobby project. Because it may benefit others as well, I have made it available for you all. But if we go live with Track Cards and release in production, it may seek some server maintenance cost, which I have no clue how much it can be. So not thinking about it until and unless many people show interest. Also it needs more work to be done on Track Cards to make it ready for production release.

I want to keep Track Cards as simple as possible. No personal data storing. No third party integration, No ads.
My sole intention is to keep track of monthly, annually, anniversary year wise spends. Example use case - to switch to a different card once annual fees waiver criteria is fulfilled or to check if we have fulfilled the airport lounge access criteria.
There are many other apps in market who does the rest, I want to build something unique and which can be of use for all of us and currently no other app provide the same.


List of cards that are currently supported -
  • ICICI Platinum Chip Credit Card
  • Amazon Pay ICICI Credit Card
  • Flipkart Axis VISA Credit Card
  • RBL Edition Classic Credit Card (Not issued any more)
  • IDFC FIRST Select Credit Card
  • Axis MY Zone Credit Card
  • ICICI Coral RuPay Credit Card
  • Airtel Axis Mastercard Credit Card
  • Swiggy HDFC Credit Card

A few of the upcoming features will be
  • email verification while sign up
  • reset password
  • ability to edit an existing card, statement record.
 
Last edited:
Here's the script that I use. Create a Google sheet and update the sheetUrl in the first line and setup trigger to run this every night between 12 - 1am.

JavaScript:
var sheetUrl = "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit#gid=0";
var momentScript = "https://cdn.jsdelivr.net/npm/moment/moment.min.js";

var bankConfig = {
  "HDFC": {
    "email": "alerts@hdfcbank.net",
    "subject": "HDFC Bank Credit Card",
    "regex": /Card .+?(\d{4}) for ([A-Za-z]+)\.? ([\d.]+) at (.+?) on (\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2})\.? Authorization code:- (\d+)/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5,
      "authcode": 6
    },
    "sheetName": "HDFC"
  },
  "Axis": {
    "email": "alerts@axisbank.com",
    "subject": "Transaction alert on Axis Bank Credit Card",
    "regex": /no. XX(\d{4}) for (INR|USD) ([\d.]+) at (.+?) on (\d{2}-\d{2}-(?:\d{2}|\d{4}) \d{2}:\d{2}:\d{2})/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Axis"
  },
  "Kotak": {
    "email": "creditcardalerts@kotak.com",
    "subject": "Kotak Bank Credit Card Transaction Alert",
    "regex": /A Transaction of (INR|USD) ([\d.]+) has been done on your Kotak Bank Credit Card No.xx(\d{4}) at (.+?) on (\d{2}-\w+-\d{4})/,
    "dateFormat": 'DD-MMM-YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Kotak"
  },
  "ICICI": {
    "email": "credit_cards@icicibank.com",
    "subject": "Transaction alert for your ICICI Bank Credit Card",
    "regex": /ICICI Bank Credit Card XX(\d{4}) has been used for a transaction of (INR|USD) ([\d.]+) on (\w{3}\s\d{2},\s\d{4} at \d{2}:\d{2}:\d{2}). Info: ([^.]+)/,
    "dateFormat": 'MMM DD, YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 6,
      "date": 4
    },
    "sheetName": "ICICI"
  },
  "SBI": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "Transaction Alert from CASHBACK SBI Card",
    "regex": /(Rs.|USD)([\d.,]+) spent on your SBI Credit Card ending (\d{4}) at ([^.]+) on (\d{2}\/\d{2}\/\d{2})/,
    "dateFormat": 'DD/MM/YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "SBI"
  },
  "SBI Recurring": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "SBI Credit Card",
    "regex": /(Rs.|USD)([\d.,]+) at ([^.]+) against (?:.+?) SBI Credit Card ending (\d{4}) on (\d{2}-\d{2}-\d{2})/,
    "dateFormat": 'DD-MM-YYYY',
    "captureGroups": {
      "card": 4,
      "currency": 1,
      "amount": 2,
      "merchant": 3,
      "date": 5
    },
    "sheetName": "SBI"
  }
};

function processTransactionEmails() {
  for (var card in bankConfig) {
    var messages = getRelevantMessages(card);
    var records = parseMessageData(messages, card);
    saveDataToSheet(records, card);
  }
  return true;
}

function getRelevantMessages(card) {
  var config = bankConfig[card];

  // Ensure the Moment.js library is loaded
  loadMomentJS();

  var startOfDay = moment().startOf('day').subtract(1, 'day');
  var startOfDayTimestamp = startOfDay.unix(); // Get the timestamp in seconds
  //startOfDayTimestamp = 1711929600;

  var query = 'from:' + config.email + ' AND subject:"' + config.subject + '" AND after:' + startOfDayTimestamp;

  Logger.log("Searching for messages using: " + query);

  var messages = [];
  var start = 0;
  var maxResults = 100;

  while (true) {
    var threads = GmailApp.search(query, start, maxResults);
    if (threads.length == 0) {
      break;
    }

    for (var i = 0; i < threads.length; i++) {
      messages = messages.concat(threads[i].getMessages());
    }

    start += maxResults;
  }

  return messages;
}

// Dynamically load the script using the URL
function loadMomentJS() {
  var response = UrlFetchApp.fetch(momentScript); // Fetch the script from the CDN
  eval(response.getContentText());  // Execute the script in the Apps Script environment
}

function parseMessageData(messages, card) {
  var config = bankConfig[card];
  var records = [];
  if (!messages || messages.length === 0) {
    Logger.log("No messages found.");
    return records;
  }

  for (var m = 0; m < messages.length; m++) {
    var text = messages[m].getPlainBody();
    text = text.replace(/\s+/g, " ").trim();

    var matches = text.match(config.regex);
    Logger.log({ text, matches });

    if (!matches) {
      continue;
    }

    var rec = {};
    for (var key in config.captureGroups) {
      var index = config.captureGroups[key];
      rec[key] = index !== null ? matches[index] : null;
    }

    // Format input date to consistent format.
    if (rec.date) {
      rec.date = moment(rec.date, config.dateFormat).format("MM/DD/YYYY HH:mm:ss")
    }

    rec.multiplier = 1;

    if (rec.merchant && rec.merchant.toLowerCase().includes("gyftr")) {
      rec.multiplier = 5;
    } else if (rec.merchant && rec.merchant.includes("smartbuy")) {
      if (rec.merchant.includes("jockey") || rec.merchant.includes("hotel") || rec.merchant.includes("pharmeasy")) {
        rec.multiplier = 10;
      }
    }

    Logger.log({ rec });
    records.push(rec);
  }
  return records;
}

function saveDataToSheet(records, card) {
  var config = bankConfig[card];
  var spreadsheet = SpreadsheetApp.openByUrl(sheetUrl);
  var sheet = spreadsheet.getSheetByName(config.sheetName);
  Logger.log("Saving " + records.length + " records");
  var lastRow = sheet.getLastRow();

  for (var r = 0; r < records.length; r++) {
    var rowData = [records[r].date, "'" + records[r].card, records[r].merchant, records[r].currency, records[r].amount];
    if (records[r].authcode) {
      rowData.push(records[r].authcode);
    }
    if (card === "HDFC") {
      rowData.push(records[r].multiplier);
    }

    sheet.appendRow(rowData);
    lastRow = sheet.getLastRow();

    sheet.getRange(lastRow, 5).setNumberFormat("0.00");
    if (records[r].authcode) {
      var authCodeCell = sheet.getRange(lastRow, 6);
      authCodeCell.setNumberFormat("@");
      authCodeCell.setValue("'" + records[r].authcode);
    }

    if (card === "HDFC") {
      var formula = "=ROUNDDOWN(E" + lastRow + "/150,0)*5*G" + lastRow;
      sheet.getRange(lastRow, 8).setFormula(formula);
    }
  }
}
how to use this script?
 
Here's the script that I use. Create a Google sheet and update the sheetUrl in the first line and setup trigger to run this every night between 12 - 1am.

JavaScript:
var sheetUrl = "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit#gid=0";
var momentScript = "https://cdn.jsdelivr.net/npm/moment/moment.min.js";

var bankConfig = {
  "HDFC": {
    "email": "alerts@hdfcbank.net",
    "subject": "HDFC Bank Credit Card",
    "regex": /Card .+?(\d{4}) for ([A-Za-z]+)\.? ([\d.]+) at (.+?) on (\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2})\.? Authorization code:- (\d+)/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5,
      "authcode": 6
    },
    "sheetName": "HDFC"
  },
  "Axis": {
    "email": "alerts@axisbank.com",
    "subject": "Transaction alert on Axis Bank Credit Card",
    "regex": /no. XX(\d{4}) for (INR|USD) ([\d.]+) at (.+?) on (\d{2}-\d{2}-(?:\d{2}|\d{4}) \d{2}:\d{2}:\d{2})/,
    "dateFormat": 'DD-MM-YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Axis"
  },
  "Kotak": {
    "email": "creditcardalerts@kotak.com",
    "subject": "Kotak Bank Credit Card Transaction Alert",
    "regex": /A Transaction of (INR|USD) ([\d.]+) has been done on your Kotak Bank Credit Card No.xx(\d{4}) at (.+?) on (\d{2}-\w+-\d{4})/,
    "dateFormat": 'DD-MMM-YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "Kotak"
  },
  "ICICI": {
    "email": "credit_cards@icicibank.com",
    "subject": "Transaction alert for your ICICI Bank Credit Card",
    "regex": /ICICI Bank Credit Card XX(\d{4}) has been used for a transaction of (INR|USD) ([\d.]+) on (\w{3}\s\d{2},\s\d{4} at \d{2}:\d{2}:\d{2}). Info: ([^.]+)/,
    "dateFormat": 'MMM DD, YYYY HH:mm:ss',
    "captureGroups": {
      "card": 1,
      "currency": 2,
      "amount": 3,
      "merchant": 6,
      "date": 4
    },
    "sheetName": "ICICI"
  },
  "SBI": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "Transaction Alert from CASHBACK SBI Card",
    "regex": /(Rs.|USD)([\d.,]+) spent on your SBI Credit Card ending (\d{4}) at ([^.]+) on (\d{2}\/\d{2}\/\d{2})/,
    "dateFormat": 'DD/MM/YYYY',
    "captureGroups": {
      "card": 3,
      "currency": 1,
      "amount": 2,
      "merchant": 4,
      "date": 5
    },
    "sheetName": "SBI"
  },
  "SBI Recurring": {
    "email": "onlinesbicard@sbicard.com",
    "subject": "SBI Credit Card",
    "regex": /(Rs.|USD)([\d.,]+) at ([^.]+) against (?:.+?) SBI Credit Card ending (\d{4}) on (\d{2}-\d{2}-\d{2})/,
    "dateFormat": 'DD-MM-YYYY',
    "captureGroups": {
      "card": 4,
      "currency": 1,
      "amount": 2,
      "merchant": 3,
      "date": 5
    },
    "sheetName": "SBI"
  }
};

function processTransactionEmails() {
  for (var card in bankConfig) {
    var messages = getRelevantMessages(card);
    var records = parseMessageData(messages, card);
    saveDataToSheet(records, card);
  }
  return true;
}

function getRelevantMessages(card) {
  var config = bankConfig[card];

  // Ensure the Moment.js library is loaded
  loadMomentJS();

  var startOfDay = moment().startOf('day').subtract(1, 'day');
  var startOfDayTimestamp = startOfDay.unix(); // Get the timestamp in seconds
  //startOfDayTimestamp = 1711929600;

  var query = 'from:' + config.email + ' AND subject:"' + config.subject + '" AND after:' + startOfDayTimestamp;

  Logger.log("Searching for messages using: " + query);

  var messages = [];
  var start = 0;
  var maxResults = 100;

  while (true) {
    var threads = GmailApp.search(query, start, maxResults);
    if (threads.length == 0) {
      break;
    }

    for (var i = 0; i < threads.length; i++) {
      messages = messages.concat(threads[i].getMessages());
    }

    start += maxResults;
  }

  return messages;
}

// Dynamically load the script using the URL
function loadMomentJS() {
  var response = UrlFetchApp.fetch(momentScript); // Fetch the script from the CDN
  eval(response.getContentText());  // Execute the script in the Apps Script environment
}

function parseMessageData(messages, card) {
  var config = bankConfig[card];
  var records = [];
  if (!messages || messages.length === 0) {
    Logger.log("No messages found.");
    return records;
  }

  for (var m = 0; m < messages.length; m++) {
    var text = messages[m].getPlainBody();
    text = text.replace(/\s+/g, " ").trim();

    var matches = text.match(config.regex);
    Logger.log({ text, matches });

    if (!matches) {
      continue;
    }

    var rec = {};
    for (var key in config.captureGroups) {
      var index = config.captureGroups[key];
      rec[key] = index !== null ? matches[index] : null;
    }

    // Format input date to consistent format.
    if (rec.date) {
      rec.date = moment(rec.date, config.dateFormat).format("MM/DD/YYYY HH:mm:ss")
    }

    rec.multiplier = 1;

    if (rec.merchant && rec.merchant.toLowerCase().includes("gyftr")) {
      rec.multiplier = 5;
    } else if (rec.merchant && rec.merchant.includes("smartbuy")) {
      if (rec.merchant.includes("jockey") || rec.merchant.includes("hotel") || rec.merchant.includes("pharmeasy")) {
        rec.multiplier = 10;
      }
    }

    Logger.log({ rec });
    records.push(rec);
  }
  return records;
}

function saveDataToSheet(records, card) {
  var config = bankConfig[card];
  var spreadsheet = SpreadsheetApp.openByUrl(sheetUrl);
  var sheet = spreadsheet.getSheetByName(config.sheetName);
  Logger.log("Saving " + records.length + " records");
  var lastRow = sheet.getLastRow();

  for (var r = 0; r < records.length; r++) {
    var rowData = [records[r].date, "'" + records[r].card, records[r].merchant, records[r].currency, records[r].amount];
    if (records[r].authcode) {
      rowData.push(records[r].authcode);
    }
    if (card === "HDFC") {
      rowData.push(records[r].multiplier);
    }

    sheet.appendRow(rowData);
    lastRow = sheet.getLastRow();

    sheet.getRange(lastRow, 5).setNumberFormat("0.00");
    if (records[r].authcode) {
      var authCodeCell = sheet.getRange(lastRow, 6);
      authCodeCell.setNumberFormat("@");
      authCodeCell.setValue("'" + records[r].authcode);
    }

    if (card === "HDFC") {
      var formula = "=ROUNDDOWN(E" + lastRow + "/150,0)*5*G" + lastRow;
      sheet.getRange(lastRow, 8).setFormula(formula);
    }
  }
}
Awesome buddy . Thank you!
 
Hi TF Community,

As we all have multiple credit cards, I am quite sure all of us have faced one or another problem while managing our credit card portfolio.
In this post we'll focus on some specific problems and try to solve them.

  • Tracking current anniversary year and or past month spending: Tracking Anniversary year spending for your credit card is important and much required for multiple reasons.
    • If you have multiple credit card with annual charges with some waiver criteria and you want to keep an eye on anniversary year spend so that once reaching the waiver limit, you can switch your expenses to another credit card for reaching waiver limit
    • If you are waiting for some benefits on your credit card, that gets activated only after a minimum spend in an anniversary year
    • If you have a capping of minimum spend in last three calendar month/billing cycle for benefits like lounge access, spa access, free golf access etc

  • Tracking all your credit card benefits at one place: Its very important to know what type of benefits you get from your credit card. Otherwise there's no point having multiple credit cards. You wont want to lose out the special benefit that your credit card provides. Sometimes keeping in mind all benefits from all of your credit cards is quite overwhelming. The most confusing part is if you get the same exact benefit from multiple cards but the only difference is capping. So its important for one to know how long you can keep using your card for a particular benefit and the perfect time to switch to another card

  • Sometimes you want to know the total rewards you have earned on a particular card since activation
  • Sometimes you want to track your past year's annual expenses on a particular card to make a decision on whether closing existing card and applying for a new credit card will be of profit or loss
  • Sometimes you want to know instantly your credit card's activation date, billing cycle date, limit and joining/annual fees

I have been looking for an app or website for tracking all of my credit card features/benefits and most importantly all transactions at the same place. Its quite hard to always open your old statement pdfs and that old email you received after activation of your card say 5 years back, to check/track any of the above information.

I know there are apps like Cred, which tracks all your credit card transactions, but the only problem with these apps is they track only current billing cycle's transactions. One cannot fetch old billing cycle's transactions.

I have been thinking of building a website which can track all of these details for solving my problems and give an one-click solution for all of them.
I am at a very initial stage of building this website and I am not planning to link it with banks or any third party, so that we can fetch all credit card info and its statement info from the bank's server.
I am planning to make it as an independent website where the user himself initially has to feed all these data, which will be a one time process or monthly (when statement gets generated) and then one can track their transactions.


Any kind of comments, compliments or even criticize are welcomed here.
I'd also expect your ideas, thought, new feature suggestions which can make the website better.
Hey.. Best of luck in advance for this upcoming website. I do support for this.... also if any feedback or suggestions I'll definitely provide it... 😊..
Waiting 😍
 
Can't login 🤔
takes some time
read:backhand-index-pointing-down:

Track Cards ->

This is only a UAT version, which has the initial features. For details, please read my first post here.

I have added a test user with some dummy data to get an idea of how things work on the website.

You can also create your own account to play around.

Please consider the following facts while using Track Cards -
  • Currently Track Cards is running in free server which has limited facilities. Henceforth, first time login/register button clicks may take around 1-2 minutes to load. You should receive a confirmation at the right-bottom of the page (toast) either in Green or Red depending on success and error responses respectively, notifying that you are good to use the app now.
  • Remember your password as its a one side encryption, it is impossible to recover. I'll develop password reset functionality soon. Dont register with invalid email, as in sometime you'll loose your account once I develop email verification feature
  • This is only for demo purposes. Refrain from uploading all your data, as this data will not be migrated. Though you can upload some data to see if things are working for you.
  • First step is to Add a Card from "Cards" tab and then you can Upload your statement from "Transactions" tab. Though I intend to avoid saving any kind of personal info like card number, cvv, expiry, name etc, at this point I'd suggest uploading dummy data instead of your actual statement
  • Until a single card is added, you won't be able to use any of the header tabs except "Cards".
  • As of now Track Cards support only limited credit cards as shared below in the list. The logic to convert monthly statement pdf's raw data into Transaction and Statement records differs for each credit card. So I was able to develop the algorithm only for the cards I am currently holding. It requires manual effort to read and observe the pattern from at least 4 to 5 statement pdfs for onboarding a new credit card. Also the current benefits (basically rewards structure) have to be implemented in the business logic for properly tracking the card usage.
  • Copy only the transactions from your statement and paste into a .txt file to upload. One month's statement data at a time should be uploaded. Please check the View Instruction before uploading. Uploading can be done from "Transactions -> Upload Statement" option

  • Under the "Home" tab, hovering mouse over a particular thumbnail image of a credit card will show its benefits in a glance
  • I have tested all features from Track Cards and made sure that they are working perfect. Still there may exists some bugs considering its only in very initial phase. Do let me know here if you find any issue.
  • Regarding enhancement to support other type of credit cards, I'll need a few statement pdfs to develop the algorithm for converting raw statement data into Transactions & Statements per credit card. But this will be the least priority and will be done only if I decide to go live in production. As I alone cant do it and I'll need some of you to share your statement pdfs for building the business logic for different cards.

Note:
As some of you were asking, I haven't thought of any paid subscription plan yet. My initial intention was to use the app only for my personal purposes and started it as a hobby project. Because it may benefit others as well, I have made it available for you all. But if we go live with Track Cards and release in production, it may seek some server maintenance cost, which I have no clue how much it can be. So not thinking about it until and unless many people show interest. Also it needs more work to be done on Track Cards to make it ready for production release.

I want to keep Track Cards as simple as possible. No personal data storing. No third party integration, No ads.
My sole intention is to keep track of monthly, annually, anniversary year wise spends. Example use case - to switch to a different card once annual fees waiver criteria is fulfilled or to check if we have fulfilled the airport lounge access criteria.
There are many other apps in market who does the rest, I want to build something unique and which can be of use for all of us and currently no other app provide the same.


List of cards that are currently supported -
  • ICICI Platinum Chip Credit Card
  • Amazon Pay ICICI Credit Card
  • Flipkart Axis VISA Credit Card
  • RBL Edition Classic Credit Card (Not issued any more)
  • IDFC FIRST Select Credit Card
  • Axis MY Zone Credit Card
  • ICICI Coral RuPay Credit Card
  • Airtel Axis Mastercard Credit Card
  • Swiggy HDFC Credit Card

A few of the upcoming features will be
  • email verification while sign up
  • reset password
  • ability to edit an existing card, statement record.
 
Back
Top