Thursday, April 9, 2009

Smallest Credit Card Payment Form

I really dislike filling out web forms. There are some fancy tools to avoid the hassle, but it would be even better if the forms weren't so long in the first place. While thinking about payments for online downloads, I wondered what the smallest possible credit card payment form would be. I came up with:

CC #
Street
Zip

I used PayPal's DirectPay documentation (PDF) as my guideline for what's required by a typical payment processor. I've separated the list of required fields into those for which you have to ask the customer and those that can be inferred from the customer's earlier responses.

  • Ask the Customer
    • Credit card number
    • Street part of address
    • Zip code
  • Infer from the above responses
    • Credit card type (inferred from CC number)
    • City (inferred from zip code)
    • State (inferred from zip code)

This exercise assumes that only U.S. payments are accepted, so I've left off the Country field. That could probably also be inferred from the zip code for many countries. Below are details about how to infer the bottom fields from the top ones.

Credit card type

Google Checkout was the first site that I saw implement this. Each credit card type has a unique prefix for their card numbers. The code for inferring the card type from the card number is pretty straightforward:

    /* cc_num is the credit card number */
    if ( cc_num.match(/^4(\d{12}|\d{15})$/) ) return 'visa';
    if ( cc_num.match(/^5[1-5]\d{14}$/) )     return 'mc';
    if ( cc_num.match(/^3(4|7)\d{13}$/) )     return 'amex';
    if ( cc_num.match(/^6011\d{12}$/) )       return 'discover';
    return 'unknown';

Once the customer is done typing his card number, you can display a credit card type logo to show that you've inferred the information.

City and State

In the common case, the city and state can be easily inferred from the zip code. Load the U.S. zip code list onto your server. Look up the customer's zip code in the list and send those on to the payment processor.

Unfortunately, some zip codes cover multiple cities. For instance, in some databases the zip code 82327 is associated with both Hanna, WY and Elmo, WY. I doubt that most credit card processors pay any attention to the exact city in the payment request. If that's right, just choose the first city in the list and submit that one.

If I'm wrong and the payment processor does care, you'll need a little more user interface. The list of zip codes with colliding cities should be fairly small. It should be trivial to include that information in a small JavaScript function. If your customer enters one of these zip codes, make an AJAX request to find out which cities are applicable and update your form on the fly to include a city selector. The customer can resolve the problem by choosing from the list of possible cities.

CC #
Street
Zip

Optional Fields

Most credit card forms ask for the credit card expiration date and the CVV2 number. PayPal doesn't require those two fields as long as you set your security thresholds low enough. For inexpensive digital downloads, it's probably acceptable to use those lower thresholds.

In the typical case, this tiny credit card form should only require about 18 key presses plus the length of the customer's street address. Since the form is so small it could easy be launched as a CSS overlay when a customer clicks a payment link. If the payment is rejected, show a rejection message in the overlay. If it's accepted, redirect the user to the link he just paid to access.

Wednesday, April 1, 2009

500 Usage: $h->push_header($field, $val)

After upgrading the LWP::UserAgent module to version 5.824, I started getting a bunch of errors like 500 Usage: $h->push_header($field, $val) in the error logs. SOAP::Lite seemed to be the top-level cause of the error. It turns out that I had an old version of SOAP::Lite (version 0.69). Upgrading to version 0.710.8 fixed the problems.