Category Archives: Uncategorized

4 Non-Obvious Tips When Developing with Credit Card Gateways

I’ve been working with credit card payment gateways as a web developer since 1997, when I built an e-commerce website for a small computer store. It’s a complex industry with myriad fees intentionally obscured behind various jargon. I’ve picked up on some of it along the way, but didn’t really start to get a solid grasp until I became a merchant account reseller myself. After reading a lot of the “behind the scenes” documentation not normally given to merchants, I’ve learned a few dirty secrets that other web developers ought to be aware of…

Include a customer, receipt, or invoice ID with every sale.

This field is normally optional with most payment gateways. However, excluding it can automatically knock the transaction into a higher risk category. This will add a 0.5 – 1.0% surcharge to the credit card sale.

Don’t rely on the gateway to catch bad expiration dates, invalid card numbers, or ZIP codes.

Most developers and merchants know that a credit card transaction costs a flat fee (generally about $0.20 – 0.30), plus a percentage of the sale (again, generally 2.3 – 2.9% for low-volume Internet merchants). However, one thing that is not frequently mentioned when opening an account is that $0.20 – 0.30 fee is charged for every call to the gateway’s API that generates a result code–sale, credit, refund, authorization, decline, etc. This includes submitting expired cards or those with invalid account numbers.

The solution is to make sure you’re properly validating the payment information on your forms. A credit card number can be checked for simple typos (such as transposed or missing digits) by running it through the Luhn algorithm. ZIP codes can be validated against a database or using a web-based API.

AVS should be set to match on both street address and ZIP code.

Address Verification System (AVS) is used to match on the billing address of a card to help reduce fraud. Many gateways allow the merchant to set how strictly an address must match to pass AVS before an authorization is approved: no matching elements, match only on ZIP code, or match on ZIP code and street address. However, if you are accepting cards that don’t match on both ZIP code and street address, the transaction is bumped to a higher risk tier. And, as with omitting an order number, the merchant will pay 0.5 – 1.0% more for that sale.

Splitting a sale across multiple transactions can get your merchant account revoked.

I’ve occasionally seen merchants split a large purchase across two or more transactions (mainly phone orders after the transaction was first declined online). This is generally done because the cardholder has a per-transaction limit on their account because they’re using a debit card or corporate/government purchase card. Don’t do it. The merchant’s bank can easily detect this pattern and will know something shady is up. Note that this is different than using two different cards for the same transaction (known as “split tender”), which is perfectly ok to do.

Share

Adding a “disable” feature to the script.aculo.us Ajax.Autocompleter

Scriptalicious’s Ajax.Autocompleter control is pretty cool. It allows a developer to add a drop-down auto-completion feature to an input box with one line of Javascript and a single div tag in the HTML. However (surprisingly), it doesn’t provide a means to turn off the autocompletion feature once it’s attached it to the target text box. There’s no method or property to disable the feature and setting the object to null has no effect (because it doesn’t affect the observers, but we’ll get to that…)

Line #8 doesn’t work as expected:

var autocomplete;

if( $('search_box') ) {
    autocomplete = new Ajax.Autocompleter("search_box", "choices", "/Autocomplete", {'frequency': 0.2, 'minChars': 3});
}

function disableAutocomplete() {
    autocomplete = null;
}

The top Google result for this issue talks about patching script.aculo.us’s “controls.js”. However, I rejected this solution because I typically load the libraries using Google’s JS API rather than serving them off my web server. Via Google the libraries load faster because they’re served off a CDN. Plus, I don’t have to pay for the bandwidth and it’s just generally more convenient.

Another solution I found is a ticket in RoR’s Trac system. Rather than hacking the source file, this used class inheritance to extend the functionality of the base Ajax.Autocompleter. This looked great because I could continue to use Google’s JS API. The bad news is that the ticket (and solution) was written over three years ago. It did not work with script.aculo.us v1.8.x or prototype.js v1.6.1.

The primary reason why this class was broken was that the methods in which the prototype library keeps track of Event observers changed considerably between versions 1.5, 1.6, and 1.6.1. Long story short: I ended up rewriting the second solution to work with prototype.js 1.6.1 and script.aculo.us v1.8.3.

My new Ajax.ToggleableAutocompleter class:

Ajax.ToggleableAutocompleter= Class.create();
Object.extend(Object.extend(Ajax.ToggleableAutocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
    this.blurHandler           = new Array();
    this.keydownHandler        = new Array();
    this.keypressHandler       = new Array();    
  },
  
  disable: function() {
    var el = this.element;
    this.hide();

    if (this.blurHandler.length==0 && this.keydownHandler.length==0 && this.keypressHandler.length==0) {
        this._registerHandlers();
    }

    this.blurHandler.each( function(handler) {
      el.stopObserving('blur', handler);
      el.getStorage().get('prototype_event_registry').unset('blur');
    });    

    this.keydownHandler.each( function(handler) {
      el.stopObserving('keydown', handler);
      el.getStorage().get('prototype_event_registry').unset('keydown');
    });      
    
    this.keypressHandler.each( function(handler) {
      el.stopObserving('keypress', handler);
      el.getStorage().get('prototype_event_registry').unset('keypress');      
    });
  },
  
  enable: function() {
    var ele=this.element;
    for (var i = 0; i < this.blurHandler.length; i++) {
      Event.observe(ele, "blur", this.blurHandler[i]);
    }
    for (var i = 0; i < this.keydownHandler.length; i++) {
      Event.observe(ele, "keydown", this.keydownHandler[i]);
    }
    for (var i = 0; i < this.keypressHandler.length; i++) {
      Event.observe(ele, "keypress", this.keypressHandler[i]);
    }    
  },
  
  onComplete: function(request) {
    this.updateChoices(request.responseText);
  },
  
  getUpdatedChoices: function() {
    this.startIndicator();

    var entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },
  
  _registerHandlers: function() {
    var o = this;
    if ( !o.element.getStorage().get('prototype_event_registry').size() > 0 ) return;
    with( o.element.getStorage().get('prototype_event_registry') ) {
       var blurEvents = get('blur');
       var keypressEvents = get('keypress');
       var keydownEvents = get('keydown');
    }
    if( blurEvents ) blurEvents.each(function(e){ 
         o.blurHandler.push(e.handler);
    });
    if( keypressEvents ) keypressEvents.each(function(e){
         o.keypressHandler.push(e.handler);
    });
    if( keydownEvents ) keydownEvents.each(function(e){
         o.keydownHandler.push(e.handler);
    });
  }
});

To use it:

var autocomplete;

if( $('search_box') ) {
    autocomplete = new Ajax.ToggleableAutocompleter("search_box", "choices", "/Autocomplete", {'frequency': 0.2, 'minChars': 3});
}

function disableAutocomplete() {
    autocomplete.disable();
}

function enableAutocomplete() {
    autocomplete.enable();
}
Share

Rebooted… again.

I’ve been half-heartily blogging for a few years now. Every so often I’ll get the urge, write something, and then leave it untouched for months. I’d be loath to come back to the blog because it felt that I somehow let the Internet down by not writing more frequently. As silly as that sounds, it seems to be a pretty common feeling/trend among bloggers. All the experts urge to post at least once a week, hopefully even daily. It’s an attempt to remain relevant in our Twitter-fueled world, it seems.

But this is an attitude that I’ve recently come to regard as misguided. It was reading this post at reddit (written by the Internet-famous Maddox) that really cemented this observation for me. People are inundated with “what’s new!” nearly 24/7 nowadays. Being topical is no longer as important as it used to be because everyone is topical. So I’ve chosen to focus this blog on quality rather than quantity.

I’ll eventually get around to importing some of my more popular old blog posts from backups. But until then, please enjoy my leisurely-written new posts. :-)

Share