Detect when payment form is completed

My goal is to disable a ‘make payment’ button until the form is filled out.

I know that there are event listeners to detect things, including:

  • errorClassAdded
  • errorClassRemoved
  • focusClassAdded
  • focusClassRemoved

But I can’t seem to devise a way to use these to detect if the form is fully filled out.

I tried to setup something where the ‘focusClassRemoved’ triggers card.tokenize(), which is then checked for errors, but it triggers errors once the first form field is unfocused and displays the errors on the form, which is obviously not a good UX experience.

What I’m really after is a function that is being called each time a field changes that lists errors (even for fields the customer hasn’t filled out yet), but doesn’t trigger the errors to be displayed.

for example:

the customer begins to fill out the ‘card’ field:
some ‘onChange’ functions runs => {errors:[‘card’,‘exp’,‘cvv’,‘zip’]}

the customer begins to fill out the ‘exp’ field:
some ‘onChange’ functions runs => {errors:[‘exp’,‘cvv’,‘zip’]}

the customer begins to fill out the ‘cvv’ field:
some ‘onChange’ functions runs => {errors:[‘cvv’,‘zip’]}

the customer begins to fill out the ‘zip’ field:
some ‘onChange’ functions runs => {errors:[‘zip’]}

the customer completes 5 digits on the ‘zip’ field:
some ‘onChange’ functions runs => {errors:[]}

Is this just not doable?

thanks!

1 Like

:wave: With the Web Payments SDK you can listen to the callbacks for cardInputEvents. Once of the callbacks is isCompletelyValid boolean. If this is true then you can enable the submit button cause the form is filled out. :slightly_smiling_face:

hmm, that seems to only validate that particular field though, which gives me a ‘false positive’ for isCompletelyValid, for example:

if I have:

card.addEventListener('focusClassRemoved',(e)=>{
  console.log(e)
})

if the person just fills out the card field only, the console.log is going to give me:

currentState:{
  hasErrorClass: false
  hasFocusClass: false
  isCompletelyValid: true
  isEmpty: false
  isPotentiallyValid: true
}

which doesn’t actually mean the entire form is filled out, just that one field. I’d need to keep track of which fields have been validated, which means I’d need to know for sure which fields are in the form (which varies from country to country).

I was also hoping to not rely on focus change because I could imagine someone filling out the form and not clicking out of the last input, which would result in the disabled submit button not changing, and the customer being confused.

Unless there’s something not in the docs, it seems like this just isn’t possible.

1 Like

I’m having the exact same problem. Were you able find a solution?

This is what I did to solve the problem. Kind of silly because it’s at a very basic level, but it works.

Since focusClassRemoved works at a field level, we need to validate each field in the card form. We start by declaring an object with all the fields initialized as false.

const cardFields = {
    cardNumber: false,
    expirationDate: false,
    cvv: false,
    postalCode: false
};

In the next line we set the card event listener:

card.addEventListener('focusClassRemoved', (e) => {
});

We then get the field and value contained in the callback:

let field = e.detail.field;
let value = e.detail.currentState.isCompletelyValid; 

Since we don’t know the field that is being validated, we set it’s value in the object using the bracket notation.

cardFields[field] = value;

Then, we make sure that all the fields in the object are set to true.

if (Object.values(cardFields).every(item => item === true)) {
    console.log('success');
} else {
    console.log('fail');
}

Inside the if-else is were we are going to code our local variable of control. I’m using Validate.js, so I’m using a checkbox.

If all fields are true:

document.getElementById('token').value = 1;
document.getElementById('token').checked = true;

If at least one field is false:

document.getElementById('token').value = 0;
document.getElementById('token').checked = false;

The whole thing looks like this:

const cardFields = {
    cardNumber: false,
    expirationDate: false,
    cvv: false,
    postalCode: false
};

card.addEventListener('focusClassRemoved', (e) => {
    let field = e.detail.field;
    let value = e.detail.currentState.isCompletelyValid;
    cardFields[field] = value;

    if (Object.values(cardFields).every(item => item === true)) {
        console.log('success');
        document.getElementById('token').value = 1;
        document.getElementById('token').checked = true;
    } else {
        console.log('fail');
        document.getElementById('token').value = 0;
        document.getElementById('token').checked = false;
    }
});

The checkbox under the card form:

<input type="checkbox" id="token" name="token" style="visibility: hidden; position: absolute">

The form passes when the checkbox is checked.

I guess that’s more or less how Square would do a validate-all-fields solution. There has to be a better way to create the cardFields object based on the content of the form, in case Square decides to add the field “name on card” but I’ll leave it there.

Hope it helps.

Thanks, @seanmart, for the idea.

1 Like