From jQuery to React
Building a better credit card form at expressPay
Introduction
I believe that paying for stuff online should be easy. That’s why, in my honest opinion, any bit of UI the user has to interact with on their way to that sweet confirmation page, has to either, be helping them get there faster or shouldn’t be there at all.
At expressPay, our service allows anyone to instantly send money to any bank account or mobile money number, buy airtime and pay for online services using, among other methods, their debit card. Our debit card form hadn’t changed in a long time. It got the job done, but, the way I saw it, could benefit from a makeover that focused on improving clarity, simplicity and user happiness.
My job was to design the credit card form, given a set of business requirements and constraints. This post is about the design considerations our team explored to arrive at the finished product.
Preparation
I knew from the get go, that I wanted to write the new form in React. Why? First off, I should mention that our current form was written with HTML, CSS and jQuery. Not only did this make the code sort of wonky, but IMHO, it made it incredibly painful to look at, and more importantly, to reason about. Secondly, I love how React made you think of your apps as you build them. React’s component driven paradigm also made it incredibly easy to write reusable components, that ultimately reduce the amount of code you write and allow you to build apps more efficiently.
Starting at the basics
At it’s most basic, a credit card form is essentially just a collection of inputs — emphasis here on inputs — that allowed a user to enter the different bits of information that make up their card details.
So my first order of business was to craft a simple, but versatile input component, that, with minimal effort, I could reuse to collect the different bits of information that made up the user’s card details.
Building the input component
First came the question of styling — Although we wanted this component to be as generic as possible, there were still business requirements concerning how the input should look. This was after all an expressPay text input component, and our style guide had clearly prescribed the baseline look for text inputs used on our site. Here’s a visual of what that looks like:
So, the takeaway here is that the input was going to come bootstrapped with with default styling that conformed to our style guide, but would still allow the consumer override these defaults if they wanted to.
Also, because we wanted to be able to take advantage of the full css spec when styling our components, we skipped inline styles and decided to go with Aphrodite.
Next came the question of state — specifically how much, if any, internal state should the component was was allowed to manage. Here, we decided that although we wanted to pass in as much as we could via props we also wanted the component to at least keep track of whether or not it had the focus, internally. This meant adding a hasFocus
field to its internal state.
Now it came identifying the different constituent parts that together, make up the text input component.
HTMLTextInput
provides the actual input into which the user typesInputIndicator
an optional component, passed in via props, that if present will be rendered to the far right end of the bordered box. In the case of card number field, thisInputIndicator
could be used to show the card issuer’s logo.BorderedBox
is a div with a solid border that containsHTMLTextInput
andInputIndicator
This is what the user perceives as the actual input, since itHTMLTextInput
doesn’t render with a border.ErrorMessage
renders an error message beneath the bordered box, if an error is present.InputContainer
: is a div that contains all the above mentioned items
With all the constituent parts laid out, I moved forward with the implementation. Let’s take a crack at it.
Here’s the TextInput
Component.
Building more specialised input components
With our generic TextInput
component in place, we could now shift our attention to creating the more specialised components that our credit card form needed. In no particular order, these are CardCVVInput
, CardExpiryInput
, CardNumberInput
and CardHolderNameInput
respectively.
Here’s the CardNumberInput
component:
Here’s the CardHolderNameInput
component:
Here’s the CardExpiryInput
component:
Here’s the CardCVVInput
component:
Oh — you’ll notice that components depend on functions and constants imported from ../utils
. Here’s the contents of that file:
Next on the agenda was, writing the CreditCardForm
component itself. This component would bring everything together by doing the following:
- Visually combining the various input components we previously put together in such a way as to achieve our overarching objective — allowing users to enter their card information.
- Holding all state necessary to capture the user’s card information, as well as the functions responsible for managing that state.
- Passing down state (and the functions that manage that state) to the various input components it encloses.
- Housing validation logic and handling errors
A word on validations and error handling.
When it came to validations we knew we wanted to simplify as much as possible. Our objective was to prevent as much bad input as we could from being entered in the first place.
As a result, you’ll see that some of our more specialised input components include an onKeyDown
function, containing logic to reject bad inputs, which they pass down to the underlying TextInput
component. We felt that this approach reduced the amount of time we spent yelling at users via error messages while allowing us get them to success more quickly.
Secondly, we chose to trigger validation logic on blur
because, by leaving an input, the user was more or less indicating that they were done providing that piece of information. We also chose to not lock the user into an erroneous field by preventing them from leaving it. The user is free to move to the next input, and come back later to fix any errors shown.
Without further ado, here’s the code for theCreditCardForm
component.
And here’s what it looks like when rendered:
The full code, along with a working demo can be found here. Feel free to take it for a spin. If you find a bug, or have feedback or a criticism, it is all very welcome. I’d love to hear from you.
Final Words…
I had a lot of fun putting this together. The payments space is not exactly sexy, but learning and understanding the user interactions behind accepting a credit card payment was really fun.