Leather: Building the Frontend

I built an app. This post represents the first installment of a three-part series describing the technology, process, and challenges involved in the creation of Leather.

Leather: Building the Frontend

Rob Schwartz / Figma

By Rob Schwartz

June 18, 2022

Over the past several months, I’ve worked with a small group of friends - now co-founders - to build an application, and I’m excited to share that we’ve completed version 1.0.0. If you want to check it out, you can find it here.

Leather, in its most basic form, is a virtual wallet. However, in addition to simply storing your credit cards, the app also recommends which of your cards you should use when you’re making an online purchase. This recommendation algorithm is Leather’s principal contribution to the space.

In case you’re unfamiliar with the complexity of the credit card rewards ecosystem, I’ll explain the problem set we’ve tried to tackle.

Every modern credit card offers a unique set of rewards as its value proposition. For example, the American Express Gold card offers 4 Amex Membership Points for each dollar spent at restaurants. The Chase Sapphire Reserve card offers 3 Ultimate Rewards Points for each dollar spent on travel. Another card may offer “2% back” on all purchases.

This can get confusing pretty quickly. When making a purchase, it can be difficult to know how that purchase will be categorized by each credit card company, let alone which specific card provides the best rewards for that purchase type.

Additionally, what’s the monetary value of a Discover Mile? Is an Amex Membership Point worth as much as a Chase Ultimate Rewards Point? If Uber is categorized as “Rideshare” with Chase, “Taxi” with Capital One, “Transit” with Citi, and “Other” with Wells Fargo and Discover, which card from which company should you use to call a ride?

This is a pain. People are definitely leaving money on the table by using the wrong card for many of their purchases, because, unless you’re a hardcore personal finance person, you probably don’t have the bandwidth to figure all of this out. Granted, this problem only confronts those who have multiple credit cards, so if you’re someone who has one or zero credit cards, you’re likely not a great candidate to find value in Leather.

But for those shoppers who do have multiple cards, each with different reward structures and point types, we think we can help you out.

When we first decided to build Leather, we considered creating a mobile app for iPhone/Android, but we decided against it for two reasons: 1) none of us has ever built a mobile app; and 2) shoppers in retail stores don’t want to have to pull out a phone and open an app to compare their cards each time they check out (i.e., too much friction). If you’re tempted by the idea of a mobile app that actually completes the purchase for you, like we were, it’s worth noting that such an app would have to leverage Apple’s or Google’s payment API, and neither of those companies has made that technology accessible to third-party developers.

Because of these pain points with a mobile app for in-store purchases, we pivoted to creating an app for online purchases: a browser extension. The benefits of a browser extension, in this case for Google Chrome, were twofold: 1) it’s relatively easy to create; and 2) it can open automatically to tell you which card to use when you’re checking out. A browser extension in this mold operates much more like a point-of-sale app than does a mobile app that’s unable to complete a purchase for you.

There are some obvious downsides to this approach as well, though - namely that nobody really uses Chrome extensions. Honey is a notable success story in the space, but your average user would be hard-pressed to name a second.

Still, we went for it, figuring that good ideas don’t occur every day.

This post is the first installment of a three-part series dedicated to Leather’s development. In this entry, I’ll focus on the extension itself: which technologies I used; how I designed and built it; and what I discovered in the process of building the user-facing component of the application (i.e., the frontend). I’ll add that I was the only person to touch the actual extension, so all the challenges, learning experiences, and problems that needed to be solved in the frontend domain fell to me.

The remaining two parts of this series will focus, first, on the backend of the application (logic, data, etc.) and, second, on the team dynamics, operational decisions, and personal lessons that defined Leather’s development.

In this post, I’ll cover the technology behind the extension (the frontend), the process of creating it, and the primary challenges I encountered during its development.


1. The Technology

I imagine that the concept of a browser extension is unfamiliar to many, so I’ll outline it here.

A browser extension is a program or application that adds functionality to a web browser. Browser extensions can be created for a browser much like a mobile app can be created for a smartphone. In that spirit, Chrome extensions are to Google Chrome what iPhone apps are to iPhones.

And like iPhone apps can leverage the power of the iPhone (think Snapchat using the iPhone’s camera functionality), Chrome extensions can harness the unique functionality of the Google Chrome browser. For Chrome extensions, this functionality can range from reading urls (which we do with Leather) to adding cookies (which we also do) or modifying the content of a webpage (which we don’t do).

This means that Chrome extensions are different from other web-based applications, due to their relationship with - and reliance upon - the browser. Notably, all Chrome extensions have to be signed off on by Google via a fairly rigorous approval process. The upside to this relationship is that Google gives you a powerful toolkit to access Chrome’s functionality with your app (an API, for the technically savvy). Basically, Google lets you use their stuff if you play by their rules.

Leather also has a user interface that takes the form of a small webpage. As with other webpages, it consists of a combination of HTML (the structured page elements), CSS (the styles applied to them), and JavaScript (to power the page’s functionality). Also like other webpages, there are numerous frameworks and libraries one could use to implement it; think React, Vue, jQuery, etc. In this case, though, I chose to not use a framework and pushed ahead with pure, vanilla JavaScript.

I made this design choice for two reasons: 1) I had been using React and other frameworks for most of my recent projects and was nostalgic for the malleability and granularity of pure JavaScript; and 2) I was unsure if Chrome extensions would be compatible with modern web frameworks. In hindsight, it seems that I could have used React for the app without issue, but by the time I realized that, I was already far enough in to be committed to vanilla JS.

Despite JavaScript’s limitations, it worked well within the rigid organizational structure that Chrome demands. For example, when you want to access Chrome’s API (toolkit), Google requires that you access it from one specific worker. This worker is just a JavaScript file, but you have to identify that file - and only that file - as the worker with the unique authority to issue API calls. So if you have a traditional script to manipulate page content, you’re actually unable to call the primary Chrome APIs from that script (a “content script”, in Chrome lingo).

I first learned this when I tried and failed to modify local storage from the login page’s content script. As it turns out, because you’re only able to access that functionality from the service worker, you need to notify that worker when you want to get anything done. To do this, Chrome provides a special message-passing function that allows you to contact the service worker from your content scripts. Much like an HTTP request, you can pass JSON objects to the service worker via one of these messages, and the service worker can ingest that message to determine the action it should take.

In terms of my own implementation, I developed of number of modules, each with a (hopefully) congruent set of functionality, and imported those to the service worker. This structure meant that the user-focused functions (logging in, creating an account, etc.), for example, were housed in one module, while credit card-related functions (searching for cards, getting card information, etc.) were housed in another. In practice, most of the functions in each module represent API calls to either the Leather backend or to Chrome’s API.

The service worker, armed with these imported modules and their accompanying functionality, reads the message it’s passed and, based on the content of that message, executes the relevant function.

This encapsulates the technical structure of the extension. Each page operates a content script in the background, and when a button is clicked, for example, the content script fires a message to the service worker. The service worker ingests that message and, having imported various modules, triggers the functionality requested in the message. Each page is pure HTML, CSS, and JavaScript. The only external styles and libraries are from FontAwesome for a handful of icons.


2. The Process

Although technical architecture and development represent a large share of the work that went into Leather, a few non-technical aspects also merit discussion.

There are three buckets of decision-making and work, outside of the app’s technical development, that I’ll address. They are: selecting the functionality to include in version 1, designing Leather’s user interface, and navigating the Chrome store approval process.

Selecting What to Include in v1

I've described the problem we’re trying to solve - namely, that credit card rewards can be so bewildering that it’s difficult and impractical to optimize how you earn them yourself. I’ve also mentioned that we first considered creating a mobile app to solve this problem but ultimately decided in favor of a browser extension.

That decision precipitated a number of functional tradeoffs and required us to determine what would be feasible to include in the first version of the app. This was compounded by there always being tradeoffs in the first version of an application.

Here’s an example of a key user flow we envisioned for the Chrome extension during its ideation:

  1. You, a user, add all of your cards, with their complete information, to the Leather wallet. This information includes your card numbers, expiration dates, and CVV codes.

  2. The extension injects a “Use your best card” button into the checkout page when you’re making a purchase. You can imagine seeing this button on Amazon’s checkout page when you would otherwise enter your card information.

  3. When you click the “Use your best card button”, Leather automatically enters your optimal card’s information for your online purchase.

In other words, our users would only have to click one button, which automatically appears at checkout, to use their best card for that purchase. This flow would have been pretty seamless once our users completed the wallet onboarding, and it was once our north star for the app. However, two roadblocks led us to reevaluate and defer that user flow to a later release.

The first was a security concern. I’ll address this further in the next installment of this series, but the thought is that storing actual credit card data both invites malicious actors to attack our app and requires us to adopt exceptionally rigorous security standards to prevent those actors from accessing our users’ data.

This isn’t to say that we don’t have modern security measures in place; we do. But we don’t claim to have the same security standards in place as, say, Bank of America does. Nor do we need to, because we don’t house any sensitive financial data. This security-minded decision prevents Leather from automatically populating your credit card data, since we don’t have it, but it also means our users can be confident that by signing up for Leather they won’t be exposing themselves to undue risk.

Instead, when users onboard with Leather, we ask them only for the types of credit cards they own. So we’ll ask you to tell us that you have, for example, the Citi Double Cash card, the Capital One Quicksilver card, and the American Express Platinum card - and that’s it. This information is sufficient for us to recommend which card to use on checkout, which is the primary functionality we want to deliver to our users. We’re okay with postponing our secondary object of auto-population to a later release, at which point we can devote appropriate resources to our information security.

The second roadblock to our one-click user flow stemmed from Google preferring that extensions don’t modify webpages. Although Google supports webpage modification via their API (the toolkit they provide to developers), they have strict standards for how you can use it, and including webpage modification in your app invites Google both to spend additional time auditing your code and to adopt a higher bar to accept your app onto their store. This is the less pressing of the two concerns, but it did contribute to our decision to postpone that user flow.

Webpage modification will become more important as reducing user friction becomes our priority, but our other goals for Leather’s first release outweighed this one. In all honesty, modifying webpages to include a “Use your best card” button also would have elongated the development process by a nontrivial amount, and we didn’t want to delay releasing v1.

This is the user flow we eventually included in v1:

  1. You, a user, register the types of cards you own, without any other identifying information about them.

  2. When you navigate to a checkout page that we support, the extension icon in your browser bar lights up green to get your attention. This is the same tactic that Honey uses.

  3. You click the extension icon, and the app opens to guide you to the checkout page.

  4. On the checkout page, you’re shown the best card for that purchase and how much money it saves you. You can also cycle through your other cards to compare them.

  5. You manually enter the recommended card's information if you need to, or you select your recommended card if it’s already saved with that online retailer.

  6. You check out.

This implementation does involve a couple clicks more than we would like, but it gets the job done for now.

Designing the UI

Leather’s design, by which I mean the design of the user interface (UI) and the app’s branding, styling, and logo, also took its share of time and energy.

We started with a completely blank slate. Although we spoke with a couple designers early on, we collectively decided that we didn’t want to spend a few thousand dollars to have someone design the app for us without any user validation of the concept. I’ve built websites and other apps before, so I volunteered to try my hand at it. If all went well, we could hire a designer down the road to rebrand the app or, at the very least, improve the existing design.

Fortunately, because I work at a startup, I’ve been exposed to a number of tools that can be leveraged for smaller organizations and side projects. Among these is Figma, a design mocking tool I highly recommend. We use it at my company, and, due to my familiarity with it from work, I chose it to mock Leather’s design.

I mentioned the page structure in the technical section earlier, but, generally, each page had to be designed from scratch, for each unique purpose. There are a couple elements that persist across the pages, like the navbar and the footer, but the primary content and layout of the app were designed on a page-by-page basis.

This process included choosing a color scheme, a font, and some basic styling guidelines to maintain consistency throughout the app. If you want to see where the design ended up without downloading the app, you can refer to this article’s cover photo, which is Leather’s main photo on the Chrome store.

In terms of styling, I landed on a light shade of green as the brand’s primary color, Lato as the text font, and a fairly minimalist layout.

The branding was a separate challenge. I went through a couple iterations of the actual logo on Figma, ranging from more angular ones to blockier ones to logos that involved using the full name. Eventually, I had the idea to create a small wallet out of two rounded rectangles - one at an angle behind the other - with some sort of ‘L’ on the front to represent the app’s name. I used Figma’s drawing tool to sketch out a cursive ‘L’ on my laptop’s trackpad, and on what I think was my third attempt, I liked the look of it and pasted it on the front of the wallet.

I’m pretty proud of the results - from the branding and styling to (what I hope is) the intuitive user interface. Although I felt confident in my development skills to build this, I didn’t know if I had the design abilities to pull it off. Now, having tackled Leather’s design, I feel armed with the tools and experience necessary to tackle any easy/intermediate-level design challenge I encounter.

Getting It Approved

On the deployment side, I faced a few hurdles in earning Google’s approval to list the app on the Chrome store. These were primarily legal concerns; the first time I submitted Leather, I hadn’t created a Privacy Policy, Cookie Policy, or a document outlining our Terms and Conditions.

In total, I submitted the app for approval four times. After the first submission, I created a Privacy Policy and deployed it to our website. After the second submission, I created our Cookie Policy, and I created our Terms and Conditions after the third. I also updated the app’s registration flow to certify that our users agree to our policies by signing up. After these changes, Leather was finally approved and online.

The complete approval process lasted about three weeks. Google can take several days to review an app for the first time, and they reviewed it four times. That being said, this was an important learning experience for me in understanding the criteria that need to be met in order to have a third party platform host your app. Having never gotten Apple, say, to approve an iPhone app, I imagine that this experience closely mirrors the approval process to get an app listed on that store, should I ever create an app for iOS.


3. The Challenges

I’ll keep this section short, as it reflects much of what I’ve already described pertaining to the creation of the extension.

On the technical side, I faced two primary difficulties.

The first was the service worker I described earlier and the unusual process by which you’re required to make API requests through that individual worker. Architecting a framework to accept messages from other scripts, then to fire a second request to the app’s actual backend, was a laborious bit of infrastructure.

Additionally, the service worker (just a JavaScript file) became cluttered very quickly with a host of disparate functions that I initially thought could only be contained in that file, due to Google’s one-worker policy. But I was convinced it had to be possible to break those functions out into more modular classes; there’s no way Honey has one service worker file, containing thousands of functions, for its entire app. This turned out to be a sufficiently niche issue for StackOverflow to not have any relevant information, so I had to dive pretty far into the docs to find the answer: that you need to specify that the service worker is a “module” in the manifest.json metadata file. Once I made this discovery, I became able to import other classes, and modularizing and adding functionality to the service worker became easy.

The second technical difficulty stemmed from the project being in vanilla JavaScript and from using no real libraries; I had to build all the UI components and functionality from scratch. I animated a dynamic dropdown list. I developed a virtual wallet. I even created a carousel to cycle through credit cards, using just HTML, CSS, and JavaScript. In developing these components, among others, I became very familiar with CSS and JavaScript’s native abilities to create animations and modify user interfaces.

On the process side, designing how Leather looked took far more time than I anticipated. Again, I have limited experience with UI/UX design, so I went through many iterations of the app, over months, before landing on styles and layouts that I felt did the app justice. I took feedback from friends, family, and my co-founders, which was a humbling experience as well. Designing a UI can be a very personal process, as it involves a number of creative, artistic, and empathetic qualities, not to mention that everyone has an opinion on a good or a bad UI. If it’s not great, it’s noticeably bad. I’ve certainly developed an appreciation for the work that designers do; it’s not easy by any measure, and they’re brave to take on that challenge.

I’d be remiss to not also mention how difficult it was maintaining motivation to complete Leather. This is a real project, one that has taken four people several months to complete. I personally spent countless hours over lunch, after work, on the weekends, and on vacation creating this app, and there have been several junctures at which I could have faltered or given up entirely. As such, my follow-through on Leather has demonstrated to me my own capacities for determination and perseverance, which had been in some doubt when I started this project last year. I’ve developed a steadfast belief in my ability to execute on future projects and endeavors, in both technical and non-technical domains, and, tangentially, I’ve learned to be more discriminate in how I spend my time.

My team and I faced scores of other challenges, but those bleed into the content surrounding Leather’s backend and the team dynamics that I’ll address in the second and third installments of this series. I’m planning to post those in the coming weeks.

Feel free to leave a comment with any thoughts about Leather’s frontend development! I finally implemented login and commenting systems for this blog, and I’d be happy to address your questions below.

Sign in to leave a comment.