UltraCart Hosted Credit Card Fields

UltraCart Hosted Credit Card Fields

Overview

UltraCart Hosted Credit Card Fields provides a mechanism to meet the new PCI 3.0 requirements without limiting your integration options or user interface.

UltraCart Hosted Credit Card Fields are seamless iframe inputs that collect the sensitive credit card data from the customer within your checkout without requiring substantial modification to your existing HTML, CSS, or JavaScript.  This will help you meet the the new data security mandates of PCI 3.0, while ensuring your organization can complete the simplest self assessment questionnaires.

  • Retains your ability to control the styling of the input
  • Helps maintain your eligibility for SAQ-A PCI certification
  • Comes standard in the legacy checkout and StoreFronts.
  • Requires minimal changes to external forms or API based checkouts.

What is the New PCI 3.0 Requirement?

As the PCI (Payment Card Industry) standard continues to evolve there are new requirements that must be implemented to maintain the security of card holder data.  With PCI 3.0, which comes into full effect in 2015, there is a new requirement that ALL sensitive payment data form fields MUST come from the PCI-DSS certified provider.   If your website is serving up the input fields associated with the PAN (primary account number 15-16 digits) or the card verification number (CVV/CVV2) then you will be exposing yourself to SAQ-EP (all 12 requirements of PCI) instead of SAQ-A (2 requirements of PCI).

Setup an Integration

Once you read the instructions below, this checklist may help you upgrade sites using UCEditor POSTs. Sorry - we don't have one for javascript checkouts yet. Those tend to vary widely.

Prerequisites

The first step in integrating UltraCart Hosted Credit Card Fields into your site is to include the three prerequisite script files.

  • jQuery
  • JSON
  • UltraCart Hosted Credit Card Fields.

The easiest way to include these three scripts is to use the block of code below.

<script type="text/javascript" src="https://token.ultracart.com/js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="https://token.ultracart.com/js/json3.min.js"></script>
<script type="text/javascript">
  // Take the version that we just included and scope it locally.  The noConflict will return $ and jQuery to their
  // original values so that the version of jQuery used for the hosted fields does not conflict with any other existing
  // version of jQuery or other JS library that use $ that are on the page.
  var jQueryHostedFields = $.noConflict(true);
  // Also make sure that we're providing consistent JSON functionality in browsers, but don't pollute existing libraries
  // by running our version in noConflict mode.  This will restore whatever is there similar to jQuery.
  // This will also make sure we have a JSON implementation in older versions of IE.
  var jsonHostedFields = JSON3.noConflict();
</script>
<script type="text/javascript" src="https://token.ultracart.com/checkout/checkout-hosted-fields-1.0.js"></script>
<script type="text/javascript">window.UltraCartHostedFields || document.write('<script src="//token.ultracart.com/checkout/checkout-hosted-fields-1.0.js?r=' + new Date().getTime() + '"><\/script>')</script>

UltraCartHostedFields.setup

There is only one method call needed to add the UltraCart Hosted Credit Card Fields to your page.   This static method processes the configuration and returns an instance object.

Parameters

ArgumentTypeDescription
jQueryjQueryAn instance of jQuery. If you're using our sample above then the value would be "jQueryHostedFields", but if you already have jQuery available on the page then you can use "jQuery"
JSONJSON

An instance of the JSON object. If you are using our sample above then the value would be "jsonHostedFields".

Most browsers provide their own JSON object which can be used by the API, but including an external version as the sample above provides consistency across all browsers and versions.

configobject
PropertyTypeRequiredDescription
sessionCredentialsObjectYesSee SessionCredentials below.
cssUrlsString[]
An optional array of CSS URLs that you would like injected into the iframe to further style the hosted input.
formString
An optional jQuery selector to locate the form. The underlying fields will be re-enabled before submission so that the masked values will be submitted.
hostedFieldsObjectYesSee HostedFields below.
overlayZIndexInteger
Change the default z-index for the overlay. If not specified then the overlay will use a z-index of 999999.
autoCopyStylesString[]

By default, the hosted fields will copy a set of common styles from the underlying field to the input. This helps to keep fonts, colors, borders, etc. looking the same within the hosted field as the underlying field. If nothing is specified, then the default set of styles copied is:

[

// Padding
"paddingBottom", "paddingLeft", "paddingRight","paddingTop",
// Text
"lineHeight", "fontSize", "fontFamily", "fontStyle", "fontWeight",
// Color
"backgroundColor", "color",
// Border
"borderBottomColor", "borderBottomLeftRadius", "borderBottomRightRadius","borderBottomStyle",
"borderBottomWidth", "borderCollapse", "borderLeftColor", "borderLeftStyle","borderLeftWidth",
"borderRightColor", "borderRightStyle", "borderRightWidth", "borderSpacing", "borderTopColor",
"borderTopLeftRadius", "borderTopRightRadius", "borderTopStyle", "borderTopWidth"

 ]

Returns
TypeDescription
UltraCartHostFieldsInstance of the hosted fields object. This variable should be stored way if any of the advanced methods need to be called.


SessionCredentials

This object contains necessary identifiers that must be passed by the UltraCart Hosted Credit Card Fields to sync up the data properly with the customer's shopping cart session.

PropertyTypeRequiredDescription
merchantIdStringYesThis is the merchant ID for the UltraCart account.
shoppingCartTokenString
This value is provided to the legacy checkout and the StoreFront checkout as $shoppingCartToken.
shoppingCartIdStringRecommended for javascript checkouts.

This is the cart.cartId for the JavaScript/REST Checkout.

If you're using the hosted fields on a web page that's collecting credit card information for updating an existing order, or an existing auto order, do NOT provide a shoppingCartId.  By leaving it off the request, the server will return back a token field that you will use to update your order/auto order record.  The shoppingCartId is ONLY for placing new orders.

HostedFields

PropertyTypeRequiredDescriptionChild Properties
creditCardNumberObjectYesConfigures the credit card number field on the checkout.
PropertyTypeRequiredDescription
selectorStringYesjQuery selector that identifies the original credit card number input field that will be transformed into a hosted field.
selectorContextElement or jQuery object
If you need the selector to find elements within a context, populate the selectorContext property. This property along with the one above is passed to a jQuery call like jQuery(selector, selectorContext). This is typically needed only if you have a heavily dynamic page and are rendering HTML using Backbone or another JavaScript MVC framework.
alertIfMissingboolean
Pop an alert if the underlying field was not found on the page. If this is false the hosted fields script will attempt to log a console message. If you ever see the error or the alert message, you are firing the setup script before the element is visible to jQuery.
tokenSelectorString
Optional jQuery selector that will be used to store the token received after the card is submitted. This is only necessary for simple form post checkouts where the session credentials only contains the merchant id and the values will be submitted to the UCEditor URL.
placeholderString
The input fields placeholder value. If your design utilizes placeholders then you can provide it here. If the underlying input field contained a placeholder then it will automatically be read and carrier through to the hosted field.
callbackfunction(card)
An optional function that will be called with a card object after the card is submitted to the server.
changefunction(maskedValue)
An optional function that can handle the change to the hosted field. The only parameter to the change function is the masked value. If no change function is provided then the default behavior is to update the underlying credit card number input field with the masked value.
errorfunction(errorMessage)
An optional function that can handle an error message from the hosted field.  The only parameter is the error message.  If no error function is provided then the default behavior is to use an alert to display the message to the customer.
html5Validity
boolean
If set to true, the hosted field library will detect if the browser supports setCustomValidity on the input and populate it with a message when an invalid card number is provided.  
creditCardCvv2ObjectRecommendedConfigured the credit card CVV2 field on the checkout.
PropertyTypeRequiredDescription
selectorStringYesjQuery selector that identifies the original credit card CVV2 input field that will be transformed into a hosted field.
selectorContextElement or jQuery object
If you need the selector to find elements within a context, populate the selectorContext property. This property along with the one above is passed to a jQuery call like jQuery(selector, selectorContext). This is typically needed only if you have a heavily dynamic page and are rendering HTML using Backbone or another JavaScript MVC framework.
alertIfMissingboolean
Pop an alert if the underlying field was not found on the page. If this is false the hosted fields script will attempt to log a console message. If you ever see the error or the alert message, you are firing the setup script before the element is visible to jQuery.
tokenSelectorString
Optional jQuery selector that will be used to store the token received after the CVV2 is submitted. This is only necessary for simple form post checkouts where the session credentials only contains the merchant id and the values will be submitted to the UCEditor URL.
placeholderString
The input fields placeholder value. If your design utilizes placeholders then you can provide it here. If the underlying input field contained a placeholder then it will automatically be read and carrier through to the hosted field.
changefunction(maskedValue)
An optional function that can handle the change to the hosted field. The only parameter to the change function is the masked value. If no change function is provided then the default behavior is to update the underlying credit card CVV2 input field with the masked value.
errorfunction(errorMessage)

An optional function that can handle an error message from the hosted field.  The only parameter is the error message.  If no error function is provided then the default behavior is to use an alert to display the message to the customer.

callbackfunction(cvv2)
An optional function that will be called with a cvv2 object after the cvv2 is submitted to the server.  The cvv2 object will contain the token and masked value.  See the Cvv2 object documented below for more details.

Card

PropertyTypeDescription
maskedCreditCardNumberStringThe masked credit card number returned after the real card number is stored.
tokenStringA token that can be passed to the UCEditor URL for simple hosted forms.
cardTypeStringThe type of card. This value can be used to select a drop box input for perform validation.

Cvv2

PropertyTypeDescription
maskedCreditCardCvv2StringThe masked credit card CVV2 returned after the real card number is stored.
tokenStringA token that can be passed to the UCEditor URL for simple hosted forms.



The instance object returned from setup also has some additional methods that can be called.  

UltraCartHostedFields.addClass

addClass and removeClass are not static methods. They regular methods you may call from your hosted fields object.

Example:

var hostedFields = UltraCartHostedFields.setup(jQuery, JSON3, {/* tons of configuration here that's been omitted for brevity */});

//later, during validation, if the credit card field is blank, add a class to the cc number overlay field like this:

hostedFields.addClass('someMissingFieldClassName', "creditCardNumber")


The next two methods addClass and removeClass can be used to adjust classes on the iframe's document body.  This allows for the CSS of the internal iframe to change state based upon behaviors taking place on the parent document.  


Parameters
ArgumentTypeDescription
classNameStringClass name that will be added to the body tag of the internal iframe document.
fieldTypeString
  • creditCardNumber
  • creditCardCvv2
  • all (default if not specified)

UltraCartHostedFields.removeClass


Parameters
ArgumentTypeDescription
classNameStringClass name that will be removed from the body tag of the internal iframe document.
fieldTypeString
  • creditCardNumber
  • creditCardCvv2
  • all (default if not specified)

UltraCartHostedFields.finished

Due to the asynchronous nature of a hosted field saving its information to the server, you need to check to make sure these operations are finished before saving your cart.  Otherwise you have a potential race condition between how fast the field can save the value and how quickly the customer clicks the finalize order button.  This method allows you to prevent this race condition.

Parameters
ArgumentTypeDescription
callbackfunctionA function to call when there are no asynchronous hosted field save operations underway.

Example:

var hostedFields = UltraCartHostedFields.setup(jQuery, JSON3, {/* tons of configuration here that's been omitted for brevity */});

// on your button click handler that saves the customers information, make a call to the finished method on the hosted fields
// object to make sure everything is saved before performing the cart update operation

$btn.on("click", function(){

hostedFields.finished(function(){

// Put your save code inside of this callback function

});

});

UltraCartHostedFields.destroy

The next method destory should be used to cleanup the hosted fields.  If you're repainting the screen using an advanced MVC JavaScript framework then make sure you destory the UltraCartHostedFields instance, repaint the page's content, and then re-initialize a new UltraCartHostedFields instance using the setup method.


Events from Hosted Fields

Once the hosted field is initialized your code can utilize standard events on the original input such as change, blur, and focus.  In addition to these standard fields, an additional event uchf:ready is triggered on the underlying input once the hosted field has fully initialized.  If your credit card number field has the id of "cardNumber" then you could use the following jQuery to setup a listener.

Listening for uchf:ready
jQuery("#cardNumber").on("uchf:ready", function(){
  console.log("Received an event that the hosted field on the card number input is ready.");
});

Due to the asynchronous nature of the hosted field loading, we recommend that you bind your event listener before the call to UltraCartHostedFields.setup.


The following sections demonstrate various types of usages for the UltraCart Hosted Credit Card Fields.

Implementing UltraCart Hosted Credit Card Fields in Simple Form Post Checkouts

The following example will implement the UltraCart Hosted Credit Card Fields.  The tokens associated with the sensitive data will automatically store into the hidden input fields that will submit to the UCEditor URL.  The underlying fields for card number and CVV2 will only contain the masked versions of the PCI sensitive data.  In this example make sure to change the two merchant ID values of DEMO to your Merchant ID.

Sample Simple Form Post
<script type="text/javascript" src="https://token.ultracart.com/js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="https://token.ultracart.com/js/json3.min.js"></script>
<script type="text/javascript">
  // Take the version that we just included and scope it locally.  The noConflict will return $ and jQuery to their
  // original values so that the version of jQuery used for the hosted fields does not conflict with any other existing
  // version of jQuery or other JS library that use $ that are on the page.
  var jQueryHostedFields = $.noConflict(true);
  // Also make sure that we're providing consistent JSON functionality in browsers, but don't pollute existing libraries
  // by running our version in noConflict mode.  This will restore whatever is there similar to jQuery.
  // This will also make sure we have a JSON implementation in older versions of IE.
  var jsonHostedFields = JSON3.noConflict();
</script>
<script type="text/javascript" src="https://token.ultracart.com/checkout/checkout-hosted-fields-1.0.js"></script>
<!-- the following line is a redundant check to ensure the hosted fields file is loaded first -->
<script type="text/javascript">window.UltraCartHostedFields || document.write('<script src="//token.ultracart.com/checkout/checkout-hosted-fields-1.0.js?r=' + new Date().getTime() + '"><\/script>')</script>
<script type="text/javascript">

  jQueryHostedFields(document).ready(function() {
    UltraCartHostedFields.setup(jQueryHostedFields, jsonHostedFields, {
          'sessionCredentials': {
            'merchantId': 'DEMO' // Change to your merchant ID
            // NOTE: This example if for a form post.  If you're using a javascript checkout, the session credentials
            // also need the 'shoppingCartId'.  But ONLY for javascript checkouts.
          },
          'cssUrls':[
//              'https://www.mysite.com/styles.css'
          ],
          'form': '#myForm',  // This is only needed for a form POST.  javascript checkouts should not be providing this.
          'hostedFields':{
            'creditCardNumber': {
              'selector': '#cardNumber',
              'tokenSelector': '#cardNumberToken' // This is only needed for a form POST.  javascript checkouts dont deal with tokens
            },
            'creditCardCvv2': {
              'selector': '#cvv2',
              'tokenSelector': '#cvv2Token' // This is only needed for a form POST.  javascript checkouts dont deal with tokens
            }
          }
        });
  });

</script>

<form action="https://secure.ultracart.com/cgi-bin/UCEditor" method="POST" id="myForm">

  <!-- Change the value of this input to your merchant ID -->
  <input type="hidden" name="merchantId" value="DEMO">

  <!-- These are the two token values that will be submitted to the UltraCart server that will sync up the sensitive data -->
  <input type="hidden" name="CreditCardNumberToken" id="cardNumberToken">
  <input type="hidden" name="CreditCardCvv2Token" id="cvv2Token">

  <input type="text" name="CreditCardNumber" id="cardNumber" value=""/>
  <br/><br/>

  <input type="text" name="CreditCardCVV2" id="cvv2"/>
  <br/><br/>

  <input type="submit" value="submit">
</form>




Javascript / REST Checkouts: HOWTO

The github home page for the responsive checkout has a section with instructions for adding hosted fields to your javascript checkout.  It's easy.  There's a detailed github gist linked within the instructions that has all the code you should need.


How do I know if Hosted Fields are even working??

First, do you see any errors in your browser console?   If you see "Ignoring duplicate readyCheck message", that's fine.   That's not an error.

Second, do you see a couple of hosted field jsp files loading?  One should load for each field (credit card, cvv) to create an iframe for each.



Third, do you see a call going out to UCCheckoutAPIHostedFields each time you enter a full value in either the credit card or cvv field?  Was the call successful?


Finally, can you put through test orders?  If you're seeing all of the above correctly, and still getting back errors about a missing credit card number, check your UltraCartHostedFields.setup block.  That's been the problem the majority of the time.   Here are the usual culprits:

  • Not changing the merchantId from demo to your merchant id.
  • Specifying the form directive for a javascript checkout.
  • Not specifying the form directive for a UCEditor POST checkout
  • Trying to send the token fields through for a javascript checkout
  • Not sending the token fields through for a UCEditor POST checkout

Doing any of the above will not prevent the hosted fields from working, but your credit card will not be sync'd with the rest of the cart when you call checkect or POST to UCEditor.   

Advanced Topics

What happens to the value in the original credit card number field?

Whatever value that is in the credit card number or cvv2 field (most likely the previously masked value) at the time setup is called will be transferred to the hosted field.  Make sure that you have loaded the original credit card number field with any value before calling setup.

What about onfocus and onblur events?

The UltraCart Hosted Credit Card Fields will automatically send these events from the internal iframe to the parent window and trigger them on the original field using the jQuery triggerHandler method.  This means if you've bound some JavaScript to your original field it will still fire without making any special changes to the code.

What about onchange event?

Whenever the hosted field updates the underlying field with a new value, the change event will be triggered.  This will allow existing JavaScript code to store the new value.  Please note, the value that the underlying form receives will also be the masked card (X's plus the last 4 digits) and the masked CVV2.

What if the browser fails to load the iframe for some reason?

If the browser is configured with the standard security model then everything should function smoothly across all major browsers and supported versions.  If the customer has changed their browser model and blocked the iframe for some reason, an alert will appear on the browser after 5 seconds if the hosted field iframes do not properly communicate back with the parent.

Why is my custom font not working within the hosted field?

While the auto copy of styles will include the font-family, you still need to import the font using a CSS file specified in the cssUrls parameter.

Is there a debug mode to determine more details of the interaction between the regular page and the hosted fields?

Yes, place the following snippet of JavaScript on your page to enable debug mode.  Please note that nothing sensitive is logged to the console, but it does help with understanding the messaging interaction that is taking place.

<script type="text/javascript">
  window.ultraCartHostedFieldsDebugMode = true;
</script>

Why am I receiving an alert window about the input type being a number?

Previously your input field for credit card number or cvv2 may have been a type="number" field instead of type="text".  The problem with type="number" is that the masked card number and cvv2 values can not be stored back into these types of inputs.  The browser will simply ignore the value and it will cause problems. Browsers will not allow the type for an input to be changed on the fly so the best option is for us to quickly alert you to the problem.

Why am I receiving an alert window about the selector not finding the element on the page?

If you receive a message like "Selector for creditCardCvv2 did not find the element on the page..." then you either have an incorrect selector for the hosted field or you called the setup method before the element was visible in the DOM.  If you're using the JavaScript API, make sure that the content is attached to the DOM before calling the hosted field setup method.  

How can I perform inline validation of the hosted fields?

Since the fields are hosted and your JavaScript never receives the full value, inline validation on the client is limited, but it's there are still some checks that you can perform.  The following sample JavaScript shows how to listen for the change events and set some classes based upon the value masked value that appears in your input field.  This code assumes that you have inputs with the associated #cardNumber and #cvv2 ids.  During your validation routine you can check for the different classes on your inputs and provide messaging to the user.  Ultimately though, the complete validation will be performed on the server side so you should make sure that your JavaScript checkout can handle validation errors properly.  

As a reminder, validation errors are returned back in the CheckoutResponse.errors property returned from the /rest/cart/checkout method or the checkout method (legacy).   It's critical that your javascript application examines the checkout response object and handles any errors returned within that object.

// as a reminder, jQueryHostedFields is a reference to a jQuery object. 
// The CSS style of the hosted fields will mimic the underlying fields, so for example, if you
// change the credit card field to have a red background, then the hosted fields will also change to red.
 
    jQueryHostedFields("#cardNumber").on('change', function(){
	  // When a change occurs, the hosted field library will add one of three classes to the underlying input: noCreditCardNumber, invalidCreditCardNumber, validCreditCardNumber

      if (!jQueryHostedFields(this).val() || jQueryHostedFields(this).hasClass("noCreditCardNumber")) {
        // No value in the credit card field
      }
      if (jQueryHostedFields(this).hasClass("invalidCreditCardNumber")) {
        // The value in the credit card field is invalid
      }
    });

    jQueryHostedFields("#cvv2").on('change', function(){
      // Clear existing classes
      jQueryHostedFields("#cvv2").removeClass("noCreditCardCvv2");
      jQueryHostedFields("#cvv2").removeClass("validCreditCardCvv2");
      jQueryHostedFields("#cvv2").removeClass("invalidCreditCardCvv2");

      // Regex for basic validation of a masked CVV2 value.
      var re = /[X]{3,4}/i;

      // Test to see if the field is empty, appears valid or is invalid
      var fieldValue = jQueryHostedFields(this).val();
      if (fieldValue === "") {
        jQueryHostedFields("#cvv2").addClass("noCreditCardCvv2");
      } else if (re.test(fieldValue)) {
        jQueryHostedFields("#cvv2").addClass("validCreditCardCvv2");
      } else {
        jQueryHostedFields("#cvv2").addClass("invalidCreditCardCvv2");
      }
    });

    // Perform initial validation
    jQueryHostedFields("#cardNumber,#cvv2").trigger("change");


FAQ

Q: I'm a small merchant, do I have to be PCI 3.0 compliant?

A: Yes, if your business accepts credit card payments at all then you have to comply with the latest PCI DSS rules.  Failure to maintain PCI compliance can lead to immediate suspension of your ability to process credit cards.

Q: What if my JavaScript checkout is doing card tokenization already?

A: This is not good enough to meet the PCI 3.0 requirements.  You will have to migrate to using UltraCart Hosted Credit Card Fields.  The good news is that this integration is simpler than the card tokenization.

Q: Can I use media queries inside of the CSS of the hosted field?

A: Yes, but keep in mind that the media query will be seeing the width of the iframe and not the width of the parent window.  If your checkout is responsive you may need to craft a more custom stylesheet to use inside of the hosted field and utilize the addClass/removeClass methods documented above instead of trying to use media queries.

Q: How do I check the card type if all I receive is the masked card number back?

A: The original credit card number field will never receive the full card number back.  In order to determine the card type associated with the card number, provide a callback function on the creditCardNumber field within your configuration object.  It will receive a card object as a parameter to the callback which will contain the card type.  Here's an example of how to use the callback function to automatically deterrmine card type. 

<script type="text/javascript">
      var hostedFields = null;
      // setup should be called each time the UI updates.
      // NOTICE THE CALLBACK BEING PASSED IN
      function setupSecureCreditCardFields(ccCallback) {
        window.ultraCartHostedFieldsDebugMode = false; // set this to true to see verbose debugging.  usually only UltraCart support will use this.
        hostedFields = UltraCartHostedFields.setup(jQuery, JSON3, {
          'sessionCredentials': {
            'merchantId': 'CHANGEME', 'shoppingCartId': app.data.cart.get('cartId') // the responsive checkout example stores the cart in a backbonejs model under the namespace app.data.cart.
          },
          'hostedFields': {
            'creditCardNumber': {
              'selector': '#creditCardNumber',
              // HERE IS THE CALLBACK BEING INSTALLED IN THE CC NUM FIELD
              'callback': ccCallback
            },
            'creditCardCvv2': {
              'selector': '#creditCardVerificationNumber'
            }
          }
        });
      }
      // teardown should be called each time a UI needs destroying.
      function teardownSecureCreditCardFields() {
        if (hostedFields != null) {
          hostedFields.destroy();
          hostedFields = null;
        }
      }
</script>

Within the Payment panel rendering, the teardown is called at the beginning and the setup called once the panel's html has rendered.  Here's an example of how it's used:

// ---------------------------------------------------------------------
// --- Payment Fields ---
// ---------------------------------------------------------------------
app.views.Payment = Backbone.View.extend({
  el: '#payment',
  events: {
    'focus input[type=text]': 'selectText',
    'change input[type=text],input[type=number],input[type=email],select': 'copyFieldToCart',
    'click .safePop': 'showSafePop',
    'click #final_btn': 'placeOrder',
    'blur input[type=text], input[type=number], input[type=email], select': 'sendGaEvent',
    'click .toggle': 'toggleSummary'
  },

  'onClose': function () {
      if(cartOptions.showSummaryBeforeFinalize) {
        this.model.off('sync reset change:subtotal change:tax change:shippingHandling change:total', this.render, this);
      }
  },

  initialize: function () {
    if(cartOptions.showSummaryBeforeFinalize) {
      this.model.on('sync reset change:subtotal change:tax change:shippingHandling change:total', this.render, this);
    }
    _.bindAll(this);
  },
  render: function () {

    // ========================================================
    // TEARDOWN!  We do this at the beginning of the render phase
    // ========================================================
    teardownSecureCreditCardFields();

    var ccType = this.model.get('creditCardType') || '';
    var ccExpMonth = this.model.get('creditCardExpirationMonth') || 0;
    var ccExpYear = this.model.get('creditCardExpirationYear') || 0;
    var sourceTypes = this.model.get('creditCardTypes') || ['AMEX', 'Discover', 'MasterCard', 'Visa' ];

    var ccTypes = [];
    _.each(sourceTypes, function (card) {
      ccTypes.push({card: card, selected: card == ccType });
    });
    var ccMonths = [
      {month: 1, name: '01', monthName: 'January', selected: 1 == ccExpMonth},
      {month: 2, name: '02', monthName: 'February', selected: 2 == ccExpMonth},
      {month: 3, name: '03', monthName: 'March', selected: 3 == ccExpMonth},
      {month: 4, name: '04', monthName: 'April', selected: 4 == ccExpMonth},
      {month: 5, name: '05', monthName: 'May', selected: 5 == ccExpMonth},
      {month: 6, name: '06', monthName: 'June', selected: 6 == ccExpMonth},
      {month: 7, name: '07', monthName: 'July', selected: 7 == ccExpMonth},
      {month: 8, name: '08', monthName: 'August', selected: 8 == ccExpMonth},
      {month: 9, name: '09', monthName: 'September', selected: 9 == ccExpMonth},
      {month: 10, name: '10', monthName: 'October', selected: 10 == ccExpMonth},
      {month: 11, name: '11', monthName: 'November', selected: 11 == ccExpMonth},
      {month: 12, name: '12', monthName: 'December', selected: 12 == ccExpMonth}
    ];

    var ccYears = [];
    for (var year = 13; year < 31; year++) {
      ccYears.push({year: year, selected: '20' + year == ccExpYear});
    }

    // If we're getting a masked cc number back, the validation rules for the CC number input need to be adjusted.
    var maskedNumber = app.data.cart.get('creditCardNumber').indexOf('XXXX-XXXX') >= 0 ? true : false;

    var context = {
      'ccTypes': ccTypes,
      'ccMonths': ccMonths,
      'ccYears': ccYears,
      'cart': this.model.attributes,
      'splitTest': cartOptions.splitTest,
      'cartOptions': window.cartOptions,
      'subtotal': this.model.get('subtotalLocalizedFormatted'),
      'tax': this.model.get('taxLocalizedFormatted'),
      'subtotalDiscount': (app.data.cart.get('subtotalDiscountLocalizedFormatted')),
      'subtotalWithDiscount' : (app.data.cart.get('subtotalWithDiscountLocalizedFormatted')),
      'shippingHandling': this.model.get('shippingHandlingLocalizedFormatted'),
      'total': this.model.get('totalLocalizedFormatted'),
      'maskedNumber': maskedNumber
    };

    this.$el.html(app.templates.payment(context));
    var that = this;
    this.$el.find('.ccvIcon').qtip({ // Get the tooltip running.
      content: {
        text: 'Loading...',
        ajax: {
          url: 'ccvhelp.html',
          loading: false
        }
      },
      show: 'hover',
      style: {
        classes: 'ccvToolTip'
      },
      position: {
        my: 'bottom center', // Bottom center of the tooltip
        at: 'top center', // ...positioned at the top center of .cvvIcon
        target: $('.ccvIcon'),
        container: that.$el,
        viewport: $(window),
        effect: false,
        adjust: {
          method: 'flip none'
        }
      }
    });

    // ========================================================
    // SETUP!  We do this after the html is rendered
    // ========================================================
    setupSecureCreditCardFields(this.hostedFieldsCallback);

    return this;

  },
  'showSafePop': function(event) {
    app.commonFunctions.safePop(event);
  },
  selectText: function (event) {
    jQuery(event.target).select();
  },
  'toggleSummary' : function (event) {
    $('.paymentSummaryHidden').slideToggle('fast', function() {
      if($(".paymentSummaryHidden").is(":visible")) {
        $('.toggle').html('[-]');
      } else {
        $('.toggle').html('[+]');
      }
    });
  },
  'sendGaEvent': function(event) {
    var fieldName = event.target.id;
    var value = jQuery.trim(jQuery(event.target).val());
    if(value && typeof ga == 'function') { // Send virtual pageview if field is filled out
      ga('send', {
        'hitType': 'pageview',
        'page': '/form/' + event.target.id,
        'title': event.target.id + ' blur'
      });
    }
  },
  'updateSecurityCodeMessage' : function(ccType) {
      // Show different help messages depending on the cc type.
    if(ccType == "AMEX") {
      var message = "(CVV / CVC) 4 digit code on the front of your card";
    } else {
      var message = "(CVV / CVC) 3 digit code on the back of your card";
    }
    this.$el.find('#ccvNote').html(message);
  },
  'copyFieldToCart': function (event) {
    var fieldName = event.target.id;
    var value = jQuery.trim(jQuery(event.target).val());
    var changes = {};


    // Add the "20" before the year
    if(fieldName === 'creditCardExpirationYear') {
      value = '20' + value;
    }

    changes[fieldName] = value;
    this.model.set(changes);
  },

// ========================================================
// CALLBACK !
// card is the object passed back from the hosted fields.  If it contains a cardType field, the upload was successful.
// ========================================================
  'hostedFieldsCallback': function(card){
    var changes = {};
   if(card && card.cardType) {
         // create a change object to update our backbonejs model.
         changes['creditCardType'] = card.cardType;
         // change the css on the <ul> field to visually indicate which card was entered.
         this.$el.find('ul#card_logos').removeClass().addClass('is_' + card.cardType );
         this.updateSecurityCodeMessage(card.cardType); // Show a different CCV help message depending on card type.
         // update the data model
          this.model.set(changes);
       } else {
         // if they uploaded junk, clear out the css class so none of the fields appear selected.
         this.$el.find('ul#card_logos').removeClass().addClass('is_nothing');
       }
  },

  placeOrder: function (event) {
    event.preventDefault();
    noCouponPop=true;
    $('#frmCheckout').validationEngine('validate');
    app.commonFunctions.checkout('Credit Card');
  }

});

And the html looks like the following code.  Bear in mind that this example is using http://handlebarsjs.com/ as a template engine, so you'll see some curly braces in the html below that's used to set the current card type during the initial render.   When the credit card number is upload, the css class on the <ul> element is adjusted to highlight the current card type.

<div class="bender-12 columns">
  <ul id="card_logos" {{#ucCompare cart.creditCardType 'false' operator="!="}}class="is_{{cart.creditCardType}}"{{/ucCompare}}>
    <li class="card_visa">Visa</li>
    <li class="card_mastercard">MasterCard</li>
    <li class="card_amex">American Express</li>
    <li class="card_discover">Discover</li>
  </ul>
</div>  



Q: I've implemented hosted fields and now the credit card number isn't masked after entering it.

A: That's okay.  The card will mask if the number is loaded from the cart or the page is reloaded.  By design, it doesn't mask upon entry anymore.