Automatically Accepting Chase Card Offers

This isn’t something I’ll write normally about but I thought it was a fun little exercise.

If you own a chase credit card, you’re most likely familiar with the concept of an offer. Each credit card you own has the option to be enrolled in a set of revolving offers that are time sensitive. For example, this month, I have a 15% off offer for Starbucks. If I use my Chase card at Starbucks, I’ll get 15% of the transaction added to my credit. Now, that’s only if I’m aware ahead of time and know I’ll be going to Starbucks and if I know I have an offer for Starbucks.

Accepting offers is a pain and the normal workflow is to go to the Chase website, go to your credit cards, look up the current offers, and then accept them. That’s a lot of steps. For about 50 offers, it’ll take you several minutes to even click and have the offers added to your card.

Now, I did this several times and at some point I got tired of it. So very quickly, I decided to at least automate the process of “clicking” on the offers. I didn’t solve the entire problem, but even this one quality of life improvement has me spending less time on the Chase UI.

I’ll post the entire code below and explain it. If you want to quickly run it, all you’ll need to do is to copy and paste the snippet in your Chrome / Firefox / Browser console while on the page containing offers for a credit card. NOTE THAT IT IS GENERALLY UNSAFE TO COPY PASTE RANDOM CODE FROM THE INTERNET INTO YOUR BROWSERS JAVASCRIPT CONSOLE. You can essentially have your login credentials ferried off to some malicious actor.

In this case, trust me! And read the code to understand it.

The code is not “production” quality so I won’t guarantee it will work in all cases:

function sleep(seconds) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, seconds * 1000)
    })
}

async function findElement(selector) {
    let tries = 0
    let element;
    while (tries < 1000) {
        element = document.querySelector(selector)
        if (!element) {
            tries++;
            await sleep(0.01);
        } else {
            return element
        }
    }
    throw new Error('unable to find element')
}

async function clearFlyout() {

    let element = await Promise.race([sleep(3), findElement('.flyout-close')])
    if (element) {
        element.click()
    }
}

async function clickFirstActiveOffer() {
    let element = await Promise.race([sleep(3), findElement('.iconAddToCard')])
    if (element) {
        element.click()
        return true
    }
    return false
}


async function clickAllOffers() {
    // start off by clearing the flyout
    await clearFlyout()
    await sleep(5)
    let foundOffer = true
    while (foundOffer) {
        foundOffer = await clickFirstActiveOffer()
        if (foundOffer) {
            await sleep(5)
            await clearFlyout()
        }
        await sleep(5);
    }
}
clickAllOffers().then(() => console.log('done'))

Let’s start with the main function clickAllOffers. It is easy to understand but the main point is that it will try to find one offer at a time. After each offer it clicks, it waits for the flyout to come up (which is why we can’t click all the offers at once :( ) it then clicks the next offer, if any. That’s it. Super simple.

findElement and sleep are utility functions. sleep wraps setTimeout to expose a promise like API. findElement just wraps querySelector in a loop so that we can “wait” for an element to show up (better to have been called waitForElement!). clickFirstActiveOffer and clearFlyout then just utilize these functions to create a flow where we either wait for an element and click it or do nothing.

That’s it. Super simple but incredibly powerful and a neat party trick that will save you tons of accumulated time.

Comments

Popular posts from this blog

Writing an API Client