Repository: braintree/braintree-web-drop-in
Pull request #661 (2020-09-14 21:14:33)

Move cardholder name field to hosted fields
### Summary

Waiting on translations for a new error message.

### Checklist

- [x] Added a changelog entry

### Authors
> List GitHub usernames for everyone who contributed to this pull request.

- @crookedneighbor 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31b69be..94876ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ unreleased
   * `hu_HU`
   * `sk_SK`
   * `zh_XC`
+- Provide validation to prevent cardholder name fields from being submitted as card numbers
+- Fix issue where hosted fields would not auto focus on card view selection
 
 1.24.0
 ------
diff --git a/src/html/main.html b/src/html/main.html
index f78ee67..36832dc 100644
--- a/src/html/main.html
+++ b/src/html/main.html
@@ -124,9 +124,7 @@
             <label for="braintree__card-view-input__cardholder-name">
               <div class="braintree-form__label">{{cardholderNameLabel}}</div>
               <div class="braintree-form__field">
-                <div class="braintree-form-cardholder-name braintree-form__hosted-field">
-                  <input class="braintree-form__raw-input" id="braintree__card-view-input__cardholder-name" type="text" placeholder="{{cardholderNamePlaceholder}}"/>
-                </div>
+                <div class="braintree-form-cardholder-name braintree-form__hosted-field"></div>
                 <div class="braintree-form__icon-container">
                   <div class="braintree-form__icon braintree-form__field-error-icon">
                     <svg width="24" height="24">
diff --git a/src/translations/ar_EG.js b/src/translations/ar_EG.js
index 5a4d16c..7396cc3 100644
--- a/src/translations/ar_EG.js
+++ b/src/translations/ar_EG.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "يجب أن يقل اسم صاحب البطاقة عن 256 حرفاً.",
   "fieldEmptyForNumber": "يرجى إدخال رقم.",
   "fieldEmptyForPostalCode": "يرجى إدخال الرمز البريدي.",
+  "fieldInvalidForCardholderName": "اسم حامل البطاقة غير صحيح.",
   "fieldInvalidForCvv": "رمز الحماية هذا غير صحيح.",
   "fieldInvalidForExpirationDate": "تاريخ انتهاء الصلاحية هذا غير صحيح.",
   "fieldInvalidForNumber": "رقم هذه البطاقة غير صحيح.",
diff --git a/src/translations/cs_CZ.js b/src/translations/cs_CZ.js
index 353f506..7a52410 100644
--- a/src/translations/cs_CZ.js
+++ b/src/translations/cs_CZ.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Jméno držitele karty musí obsahovat méně než 256 znaků.",
   "fieldEmptyForNumber": "Vyplňte číslo.",
   "fieldEmptyForPostalCode": "Vyplňte PSČ.",
+  "fieldInvalidForCardholderName": "Toto jméno držitele karty není platné.",
   "fieldInvalidForCvv": "Tento bezpečnostní kód není platný.",
   "fieldInvalidForExpirationDate": "Toto datum vypršení platnosti není platné.",
   "fieldInvalidForNumber": "Toto číslo karty není platné.",
diff --git a/src/translations/da_DK.js b/src/translations/da_DK.js
index 285a538..ddff24f 100644
--- a/src/translations/da_DK.js
+++ b/src/translations/da_DK.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Kortejerens navn skal være mindre end 256 tegn.",
   "fieldEmptyForNumber": "Du skal angive et nummer.",
   "fieldEmptyForPostalCode": "Du skal angive et postnummer.",
+  "fieldInvalidForCardholderName": "Kortindehaverens navn er ugyldigt.",
   "fieldInvalidForCvv": "Sikkerhedskoden er ugyldig.",
   "fieldInvalidForExpirationDate": "Udløbsdatoen er ugyldig.",
   "fieldInvalidForNumber": "Kortnummeret er ugyldigt.",
diff --git a/src/translations/de_DE.js b/src/translations/de_DE.js
index bae87db..d401baa 100644
--- a/src/translations/de_DE.js
+++ b/src/translations/de_DE.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Der Name des Karteninhabers darf 255 Zeichen nicht übersteigen.",
   "fieldEmptyForNumber": "Geben Sie die Nummer ein.",
   "fieldEmptyForPostalCode": "Geben Sie die PLZ ein.",
+  "fieldInvalidForCardholderName": "Der Name des Karteninhabers ist nicht gültig.",
   "fieldInvalidForCvv": "Die Kartenprüfnummer ist ungültig.",
   "fieldInvalidForExpirationDate": "Das Ablaufdatum ist ungültig.",
   "fieldInvalidForNumber": "Die Kreditkartennummer ist ungültig.",
diff --git a/src/translations/el_GR.js b/src/translations/el_GR.js
index 495bd08..7db4247 100644
--- a/src/translations/el_GR.js
+++ b/src/translations/el_GR.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Το όνομα του κατόχου της κάρτας πρέπει να περιέχει λιγότερους από 256 χαρακτήρες.",
   "fieldEmptyForNumber": "Συμπληρώστε έναν αριθμό.",
   "fieldEmptyForPostalCode": "Συμπληρώστε έναν ταχυδρομικό κώδικα.",
+  "fieldInvalidForCardholderName": "Αυτό το όνομα κατόχου κάρτας δεν είναι έγκυρο.",
   "fieldInvalidForCvv": "Αυτός ο κωδικός ασφαλείας δεν είναι έγκυρος.",
   "fieldInvalidForExpirationDate": "Αυτή η ημερομηνία λήξης δεν είναι έγκυρη.",
   "fieldInvalidForNumber": "Αυτός ο αριθμός κάρτας δεν είναι έγκυρος.",
diff --git a/src/translations/en_AU.js b/src/translations/en_AU.js
index b75a5c8..c6c70d2 100644
--- a/src/translations/en_AU.js
+++ b/src/translations/en_AU.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Cardholder name must be less than 256 characters.",
   "fieldEmptyForNumber": "Please fill out a number.",
   "fieldEmptyForPostalCode": "Please fill out a postcode.",
+  "fieldInvalidForCardholderName": "This cardholder name isn't valid.",
   "fieldInvalidForCvv": "This security code is not valid.",
   "fieldInvalidForExpirationDate": "This expiry date is not valid.",
   "fieldInvalidForNumber": "This card number is not valid.",
diff --git a/src/translations/en_GB.js b/src/translations/en_GB.js
index 4c6a1e2..1d4e65d 100644
--- a/src/translations/en_GB.js
+++ b/src/translations/en_GB.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Cardholder name must be less than 256 characters.",
   "fieldEmptyForNumber": "Please fill in a number.",
   "fieldEmptyForPostalCode": "Please fill in a postcode.",
+  "fieldInvalidForCardholderName": "This cardholder name isn't valid.",
   "fieldInvalidForCvv": "This security code is not valid.",
   "fieldInvalidForExpirationDate": "This expiry date is not valid.",
   "fieldInvalidForNumber": "This card number is not valid.",
diff --git a/src/translations/en_IN.js b/src/translations/en_IN.js
index fc47c62..3e4b6fc 100644
--- a/src/translations/en_IN.js
+++ b/src/translations/en_IN.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Cardholder name must have less than 256 characters.",
   "fieldEmptyForNumber": "Please enter a number.",
   "fieldEmptyForPostalCode": "Please enter a PIN code.",
+  "fieldInvalidForCardholderName": "This cardholder name isn't valid.",
   "fieldInvalidForCvv": "This security code isn't valid.",
   "fieldInvalidForExpirationDate": "This expiration date isn't valid.",
   "fieldInvalidForNumber": "This card number isn't valid.",
diff --git a/src/translations/en_US.js b/src/translations/en_US.js
index ca9770d..05d48d5 100644
--- a/src/translations/en_US.js
+++ b/src/translations/en_US.js
@@ -20,6 +20,7 @@ module.exports = {
   fieldEmptyForCardholderName: 'Please fill out a cardholder name.',
   fieldEmptyForNumber: 'Please fill out a card number.',
   fieldEmptyForPostalCode: 'Please fill out a postal code.',
+  fieldInvalidForCardholderName: 'This cardholder name is not valid.',
   fieldInvalidForCvv: 'This security code is not valid.',
   fieldInvalidForExpirationDate: 'This expiration date is not valid.',
   fieldInvalidForNumber: 'This card number is not valid.',
diff --git a/src/translations/es_ES.js b/src/translations/es_ES.js
index bdc61a2..ce46690 100644
--- a/src/translations/es_ES.js
+++ b/src/translations/es_ES.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "El nombre del titular de la tarjeta debe tener menos de 256 caracteres.",
   "fieldEmptyForNumber": "Escribe un número.",
   "fieldEmptyForPostalCode": "Escribe el código postal.",
+  "fieldInvalidForCardholderName": "El nombre del titular de la tarjeta no es válido.",
   "fieldInvalidForCvv": "Este código de seguridad no es válido.",
   "fieldInvalidForExpirationDate": "Esta fecha de vencimiento no es válida.",
   "fieldInvalidForNumber": "Este número de tarjeta no es válido.",
diff --git a/src/translations/es_XC.js b/src/translations/es_XC.js
index 5a3282a..39162bd 100644
--- a/src/translations/es_XC.js
+++ b/src/translations/es_XC.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "El nombre del titular de la tarjeta debe tener menos de 256 caracteres.",
   "fieldEmptyForNumber": "Ingrese un número.",
   "fieldEmptyForPostalCode": "Ingrese un código postal.",
+  "fieldInvalidForCardholderName": "El nombre del titular de la tarjeta no es válido.",
   "fieldInvalidForCvv": "Este código de seguridad no es válido.",
   "fieldInvalidForExpirationDate": "Esta fecha de vencimiento no es válida.",
   "fieldInvalidForNumber": "Este número de tarjeta no es válido.",
diff --git a/src/translations/fi_FI.js b/src/translations/fi_FI.js
index 1887a6d..b70cf20 100644
--- a/src/translations/fi_FI.js
+++ b/src/translations/fi_FI.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Kortin haltijan nimen on oltava alle 256 merkkiä.",
   "fieldEmptyForNumber": "Täytä numero.",
   "fieldEmptyForPostalCode": "Täytä postinumero.",
+  "fieldInvalidForCardholderName": "Kortinhaltijan nimi ei ole kelvollinen.",
   "fieldInvalidForCvv": "Tämä turvakoodi ei ole kelvollinen.",
   "fieldInvalidForExpirationDate": "Tämä päättymispäivämäärä ei ole kelvollinen.",
   "fieldInvalidForNumber": "Tämä kortin numero ei ole kelvollinen.",
diff --git a/src/translations/fr_CA.js b/src/translations/fr_CA.js
index 394bb4f..7fad107 100644
--- a/src/translations/fr_CA.js
+++ b/src/translations/fr_CA.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Le nom du titulaire de la carte doit contenir moins de 256 caractères.",
   "fieldEmptyForNumber": "Veuillez saisir un numéro.",
   "fieldEmptyForPostalCode": "Veuillez saisir un code postal.",
+  "fieldInvalidForCardholderName": "Le nom de ce titulaire de la carte n'est pas valide.",
   "fieldInvalidForCvv": "Ce cryptogramme visuel n'est pas valide.",
   "fieldInvalidForExpirationDate": "Cette date d'expiration n'est pas valide.",
   "fieldInvalidForNumber": "Ce numéro de carte n'est pas valide.",
diff --git a/src/translations/fr_FR.js b/src/translations/fr_FR.js
index d525655..32565b7 100644
--- a/src/translations/fr_FR.js
+++ b/src/translations/fr_FR.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Le nom du titulaire de la carte doit contenir moins de 256 caractères.",
   "fieldEmptyForNumber": "Entrez un numéro.",
   "fieldEmptyForPostalCode": "Entrez un code postal.",
+  "fieldInvalidForCardholderName": "Le nom de ce titulaire de la carte est incorrect.",
   "fieldInvalidForCvv": "Ce cryptogramme visuel n'est pas valide.",
   "fieldInvalidForExpirationDate": "Cette date d'expiration n'est pas valide.",
   "fieldInvalidForNumber": "Ce numéro de carte n'est pas valide.",
diff --git a/src/translations/fr_XC.js b/src/translations/fr_XC.js
index 1a28251..c0c2c2d 100644
--- a/src/translations/fr_XC.js
+++ b/src/translations/fr_XC.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Le nom du titulaire de la carte doit comporter moins de 256 caractères.",
   "fieldEmptyForNumber": "Indiquez un numéro.",
   "fieldEmptyForPostalCode": "Indiquez un code postal.",
+  "fieldInvalidForCardholderName": "Le nom de ce titulaire de la carte est incorrect.",
   "fieldInvalidForCvv": "Ce cryptogramme visuel n'est pas valide.",
   "fieldInvalidForExpirationDate": "Cette date d'expiration n'est pas valide.",
   "fieldInvalidForNumber": "Ce numéro de carte n'est pas valide.",
diff --git a/src/translations/he_IL.js b/src/translations/he_IL.js
index 8c36293..28d34ca 100644
--- a/src/translations/he_IL.js
+++ b/src/translations/he_IL.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "שם בעל הכרטיס יכול להכיל 256 תווים לכל היותר.",
   "fieldEmptyForNumber": "עליך למלא את המספר.",
   "fieldEmptyForPostalCode": "עליך למלא את המיקוד.",
+  "fieldInvalidForCardholderName": "שם בעל הכרטיס אינו חוקי.",
   "fieldInvalidForCvv": "קוד אבטחה זה אינו חוקי.",
   "fieldInvalidForExpirationDate": "תאריך פקיעת התוקף הזה אינו חוקי.",
   "fieldInvalidForNumber": "מספר כרטיס האשראי הזה אינו חוקי.",
diff --git a/src/translations/hu_HU.js b/src/translations/hu_HU.js
index 61ebfe2..a206bcf 100644
--- a/src/translations/hu_HU.js
+++ b/src/translations/hu_HU.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "A kártyatulajdonos neve nem lehet hosszabb mint 256 karakter.",
   "fieldEmptyForNumber": "Töltse ki a számot.",
   "fieldEmptyForPostalCode": "Töltse ki az irányítószámot.",
+  "fieldInvalidForCardholderName": "Ez a kártyabirtokos neve nem érvényes.",
   "fieldInvalidForCvv": "Ez a biztonsági kód nem érvényes.",
   "fieldInvalidForExpirationDate": "Ez a lejárati dátum nem érvényes.",
   "fieldInvalidForNumber": "Ez a kártyaszám nem érvényes.",
diff --git a/src/translations/id_ID.js b/src/translations/id_ID.js
index c05e8ec..d9468dc 100644
--- a/src/translations/id_ID.js
+++ b/src/translations/id_ID.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Nama pemegang kartu harus kurang dari 256 karakter.",
   "fieldEmptyForNumber": "Masukkan nomor.",
   "fieldEmptyForPostalCode": "Masukkan kode pos.",
+  "fieldInvalidForCardholderName": "Nama pemilik kartu ini tidak valid.",
   "fieldInvalidForCvv": "Kode keamanan ini tidak valid.",
   "fieldInvalidForExpirationDate": "Tanggal akhir berlaku ini tidak valid.",
   "fieldInvalidForNumber": "Nomor kartu ini tidak valid.",
diff --git a/src/translations/it_IT.js b/src/translations/it_IT.js
index e952165..7f4dacb 100644
--- a/src/translations/it_IT.js
+++ b/src/translations/it_IT.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Il nome del titolare della carta deve avere meno di 256 caratteri.",
   "fieldEmptyForNumber": "Immetti il numero di carta.",
   "fieldEmptyForPostalCode": "Immetti il CAP.",
+  "fieldInvalidForCardholderName": "Il nome del titolare non è valido.",
   "fieldInvalidForCvv": "Il codice di sicurezza non è valido.",
   "fieldInvalidForExpirationDate": "La data di scadenza non è valida.",
   "fieldInvalidForNumber": "Il numero di carta non è valido.",
diff --git a/src/translations/ja_JP.js b/src/translations/ja_JP.js
index 6f82eb0..7f0329a 100644
--- a/src/translations/ja_JP.js
+++ b/src/translations/ja_JP.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "カード保有者の名前は256文字未満にしてください。",
   "fieldEmptyForNumber": "番号を入力してください。",
   "fieldEmptyForPostalCode": "郵便番号を入力してください。",
+  "fieldInvalidForCardholderName": "このカード保有者の名前は無効です。",
   "fieldInvalidForCvv": "このセキュリティコードは無効です。",
   "fieldInvalidForExpirationDate": "この有効期限は無効です。",
   "fieldInvalidForNumber": "このカード番号は無効です。",
diff --git a/src/translations/ko_KR.js b/src/translations/ko_KR.js
index 183e996..838c08d 100644
--- a/src/translations/ko_KR.js
+++ b/src/translations/ko_KR.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "카드 소유자 이름은 256자 미만이어야 합니다.",
   "fieldEmptyForNumber": "번호를 입력하세요.",
   "fieldEmptyForPostalCode": "우편번호를 입력하세요.",
+  "fieldInvalidForCardholderName": "이 카드 소유자 이름은 유효하지 않습니다.",
   "fieldInvalidForCvv": "이 보안 코드가 올바르지 않습니다.",
   "fieldInvalidForExpirationDate": "이 만료일이 올바르지 않습니다.",
   "fieldInvalidForNumber": "이 카드 번호가 올바르지 않습니다.",
diff --git a/src/translations/nl_NL.js b/src/translations/nl_NL.js
index 1e82878..184fdac 100644
--- a/src/translations/nl_NL.js
+++ b/src/translations/nl_NL.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "De naam van de kaarthouder moet korter zijn dan 256 tekens.",
   "fieldEmptyForNumber": "Vul een nummer in.",
   "fieldEmptyForPostalCode": "Vul een postcode in.",
+  "fieldInvalidForCardholderName": "Naam kaarthouder is ongeldig.",
   "fieldInvalidForCvv": "Deze CSC is ongeldig.",
   "fieldInvalidForExpirationDate": "Deze vervaldatum is ongeldig.",
   "fieldInvalidForNumber": "Dit creditcardnummer is ongeldig.",
diff --git a/src/translations/no_NO.js b/src/translations/no_NO.js
index e1b0d6f..c3a4dfe 100644
--- a/src/translations/no_NO.js
+++ b/src/translations/no_NO.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Makslengden for kortinnehaverens navn er 256 tegn.",
   "fieldEmptyForNumber": "Oppgi et nummer.",
   "fieldEmptyForPostalCode": "Oppgi et postnummer.",
+  "fieldInvalidForCardholderName": "Denne kortinnehaverens navn er ikke gyldig.",
   "fieldInvalidForCvv": "Denne sikkerhetskoden er ikke gyldig.",
   "fieldInvalidForExpirationDate": "Denne utløpsdatoen er ikke gyldig.",
   "fieldInvalidForNumber": "Dette kortnummeret er ikke gyldig.",
diff --git a/src/translations/pl_PL.js b/src/translations/pl_PL.js
index cede75d..0642252 100644
--- a/src/translations/pl_PL.js
+++ b/src/translations/pl_PL.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Imię i nazwisko posiadacza karty musi mieć mniej niż 256 znaków.",
   "fieldEmptyForNumber": "Podaj numer.",
   "fieldEmptyForPostalCode": "Podaj kod pocztowy.",
+  "fieldInvalidForCardholderName": "To imię i nazwisko posiadacza karty jest nieprawidłowe.",
   "fieldInvalidForCvv": "Podany kod bezpieczeństwa jest nieprawidłowy.",
   "fieldInvalidForExpirationDate": "Podana data ważności jest nieprawidłowa.",
   "fieldInvalidForNumber": "Podany numer karty jest nieprawidłowy.",
diff --git a/src/translations/pt_BR.js b/src/translations/pt_BR.js
index b3ecf44..42f936e 100644
--- a/src/translations/pt_BR.js
+++ b/src/translations/pt_BR.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "O nome do titular do cartão deve ter menos de 256 caracteres.",
   "fieldEmptyForNumber": "Informe um número.",
   "fieldEmptyForPostalCode": "Informe um CEP.",
+  "fieldInvalidForCardholderName": "O nome do titular do cartão é inválido.",
   "fieldInvalidForCvv": "Este código de segurança não é válido.",
   "fieldInvalidForExpirationDate": "Esta data de vencimento não é válida.",
   "fieldInvalidForNumber": "O número do cartão não é válido.",
diff --git a/src/translations/pt_PT.js b/src/translations/pt_PT.js
index fe08eb2..6539b49 100644
--- a/src/translations/pt_PT.js
+++ b/src/translations/pt_PT.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "O nome do titular do cartão tem de ter menos de 256 carateres.",
   "fieldEmptyForNumber": "Introduza um número.",
   "fieldEmptyForPostalCode": "Introduza o código postal.",
+  "fieldInvalidForCardholderName": "Este nome do titular do cartão não é válido.",
   "fieldInvalidForCvv": "Este código de segurança não é válido.",
   "fieldInvalidForExpirationDate": "Esta data de validade não é correta.",
   "fieldInvalidForNumber": "Este número de cartão não é válido.",
diff --git a/src/translations/ru_RU.js b/src/translations/ru_RU.js
index 8763d3f..d1576c9 100644
--- a/src/translations/ru_RU.js
+++ b/src/translations/ru_RU.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Имя владельца карты должно содержать не более 256 символов.",
   "fieldEmptyForNumber": "Введите номер.",
   "fieldEmptyForPostalCode": "Укажите почтовый индекс.",
+  "fieldInvalidForCardholderName": "Недопустимое имя владельца карты.",
   "fieldInvalidForCvv": "Этот код безопасности недействителен.",
   "fieldInvalidForExpirationDate": "Эта дата окончания срока действия недействительна.",
   "fieldInvalidForNumber": "Этот номер карты недействителен.",
diff --git a/src/translations/sk_SK.js b/src/translations/sk_SK.js
index 00a5bc9..31c8f54 100644
--- a/src/translations/sk_SK.js
+++ b/src/translations/sk_SK.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Meno majiteľa karty musí obsahovať menej ako 256 znakov.",
   "fieldEmptyForNumber": "Zadajte číslo.",
   "fieldEmptyForPostalCode": "Zadajte PSČ.",
+  "fieldInvalidForCardholderName": "Toto meno majiteľa karty je neplatné.",
   "fieldInvalidForCvv": "Tento bezpečnostný kód je neplatný.",
   "fieldInvalidForExpirationDate": "Tento dátum skončenia platnosti je neplatný.",
   "fieldInvalidForNumber": "Toto číslo karty je neplatné.",
diff --git a/src/translations/sv_SE.js b/src/translations/sv_SE.js
index 2297482..de2f7bd 100644
--- a/src/translations/sv_SE.js
+++ b/src/translations/sv_SE.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "Kortinnehavarens namn måste vara kortare än 256 tecken.",
   "fieldEmptyForNumber": "Fyll i ett nummer.",
   "fieldEmptyForPostalCode": "Fyll i ett postnummer.",
+  "fieldInvalidForCardholderName": "Namnet på kortinnehavaren är ogiltigt.",
   "fieldInvalidForCvv": "Den här säkerhetskoden är inte giltig.",
   "fieldInvalidForExpirationDate": "Det här utgångsdatumet är inte giltigt.",
   "fieldInvalidForNumber": "Det här kortnumret är inte giltigt.",
diff --git a/src/translations/th_TH.js b/src/translations/th_TH.js
index 30585f9..a7354bf 100644
--- a/src/translations/th_TH.js
+++ b/src/translations/th_TH.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "ชื่อผู้ถือบัตรจะต้องไม่เกิน 256 อักขระ",
   "fieldEmptyForNumber": "โปรดกรอกหมายเลข",
   "fieldEmptyForPostalCode": "โปรดกรอกรหัสไปรษณีย์",
+  "fieldInvalidForCardholderName": "ชื่อผู้ถือบัตรนี้ไม่ถูกต้องชื่อผู้ถือบัตรนี้ไม่ถูกต้อง",
   "fieldInvalidForCvv": "รหัสความปลอดภัยนี้ไม่ถูกต้อง",
   "fieldInvalidForExpirationDate": "วันที่หมดอายุนี้ไม่ถูกต้อง",
   "fieldInvalidForNumber": "หมายเลขบัตรนี้ไม่ถูกต้อง",
diff --git a/src/translations/zh_CN.js b/src/translations/zh_CN.js
index 19b6d68..216d2dc 100644
--- a/src/translations/zh_CN.js
+++ b/src/translations/zh_CN.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "持卡人姓名必须少于256个字符。",
   "fieldEmptyForNumber": "请填写一个号码。",
   "fieldEmptyForPostalCode": "请填写邮政编码。",
+  "fieldInvalidForCardholderName": "此持卡人姓名无效。",
   "fieldInvalidForCvv": "此安全代码无效。",
   "fieldInvalidForExpirationDate": "此有效期限无效。",
   "fieldInvalidForNumber": "此卡号无效。",
diff --git a/src/translations/zh_HK.js b/src/translations/zh_HK.js
index 08a0958..77b4c97 100644
--- a/src/translations/zh_HK.js
+++ b/src/translations/zh_HK.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "持卡人姓名必須少於 256 個字元。",
   "fieldEmptyForNumber": "請填寫號碼。",
   "fieldEmptyForPostalCode": "請填寫郵遞區號。",
+  "fieldInvalidForCardholderName": "持卡人姓名無效。",
   "fieldInvalidForCvv": "此安全代碼無效。",
   "fieldInvalidForExpirationDate": "此到期日無效。",
   "fieldInvalidForNumber": "此卡號無效。",
diff --git a/src/translations/zh_TW.js b/src/translations/zh_TW.js
index 789a355..c0190cd 100644
--- a/src/translations/zh_TW.js
+++ b/src/translations/zh_TW.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "持卡人姓名不能超過 256 個字元。",
   "fieldEmptyForNumber": "請填妥號碼。",
   "fieldEmptyForPostalCode": "請填寫郵遞區號。",
+  "fieldInvalidForCardholderName": "此持卡人姓名無效。",
   "fieldInvalidForCvv": "這組安全代碼無效。",
   "fieldInvalidForExpirationDate": "此到期日無效。",
   "fieldInvalidForNumber": "此卡號無效。",
diff --git a/src/translations/zh_XC.js b/src/translations/zh_XC.js
index 25851cf..26d2cbb 100644
--- a/src/translations/zh_XC.js
+++ b/src/translations/zh_XC.js
@@ -20,6 +20,7 @@ module.exports = {
   "fieldTooLongForCardholderName": "持卡人姓名必须少于256个字符。",
   "fieldEmptyForNumber": "请填写号码。",
   "fieldEmptyForPostalCode": "请填写邮政编码。",
+  "fieldInvalidForCardholderName": "此持卡人姓名无效。",
   "fieldInvalidForCvv": "此验证码无效。",
   "fieldInvalidForExpirationDate": "此有效期限无效。",
   "fieldInvalidForNumber": "此卡号无效。",
diff --git a/src/views/payment-sheet-views/card-view.js b/src/views/payment-sheet-views/card-view.js
index 12050cd..63b8a2a 100644
--- a/src/views/payment-sheet-views/card-view.js
+++ b/src/views/payment-sheet-views/card-view.js
@@ -30,19 +30,19 @@ CardView.ID = CardView.prototype.ID = constants.paymentOptionIDs.card;
 
 CardView.prototype.initialize = function () {
   var cvvFieldGroup, postalCodeFieldGroup, hfOptions;
-  var cardholderNameField = this.getElementById('cardholder-name-field-group');
+  var cardholderNameGroup = this.getElementById('cardholder-name-field-group');
   var cardIcons = this.getElementById('card-view-icons');
 
   this.merchantConfiguration = this.model.merchantConfiguration.card || {};
   this.merchantConfiguration.vault = this.merchantConfiguration.vault || {};
+  this.hasCardholderName = Boolean(this.merchantConfiguration.cardholderName);
+  this.cardholderNameRequired = this.hasCardholderName && this.merchantConfiguration.cardholderName.required === true;
   hfOptions = this._generateHostedFieldsOptions();
 
   cardIcons.innerHTML = cardIconHTML;
   this._hideUnsupportedCardIcons();
 
   this.hasCVV = hfOptions.fields.cvv;
-  this.hasCardholderName = Boolean(this.merchantConfiguration.cardholderName);
-  this.cardholderNameInput = cardholderNameField.querySelector('input');
   this.saveCardInput = this.getElementById('save-card-input');
   this.cardNumberIcon = this.getElementById('card-number-icon');
   this.cardNumberIconSvg = this.getElementById('card-number-icon-svg');
@@ -50,22 +50,10 @@ CardView.prototype.initialize = function () {
   this.cvvIconSvg = this.getElementById('cvv-icon-svg');
   this.cvvLabelDescriptor = this.getElementById('cvv-label-descriptor');
   this.fieldErrors = {};
-  this.extraInputs = [
-    {
-      fieldName: 'cardholderName',
-      enabled: this.hasCardholderName,
-      required: this.hasCardholderName && this.merchantConfiguration.cardholderName.required,
-      requiredError: this.strings.fieldEmptyForCardholderName,
-      validations: [
-        {
-          isValid: function (value) {
-            return value.length < 256;
-          },
-          error: this.strings.fieldTooLongForCardholderName
-        }
-      ]
-    }
-  ];
+
+  if (!this.hasCardholderName) {
+    cardholderNameGroup.parentNode.removeChild(cardholderNameGroup);
+  }
 
   if (!this.hasCVV) {
     cvvFieldGroup = this.getElementById('cvv-field-group');
@@ -77,14 +65,6 @@ CardView.prototype.initialize = function () {
     postalCodeFieldGroup.parentNode.removeChild(postalCodeFieldGroup);
   }
 
-  this.extraInputs.forEach(function (extraInput) {
-    if (extraInput.enabled) {
-      this._setupExtraInput(extraInput);
-    } else {
-      this._removeExtraInput(extraInput);
-    }
-  }.bind(this));
-
   if (!this.model.isGuestCheckout && this.merchantConfiguration.vault.allowVaultCardOverride === true) {
     classList.remove(this.getElementById('save-card-field-group'), 'braintree-hidden');
   }
@@ -118,45 +98,6 @@ CardView.prototype.initialize = function () {
   }.bind(this));
 };
 
-CardView.prototype._setupExtraInput = function (extraInput) {
-  var self = this;
-  var fieldNameKebab = camelCaseToKebabCase(extraInput.fieldName);
-  var field = this.getElementById(fieldNameKebab + '-field-group');
-  var input = field.querySelector('input');
-  var nameContainer = field.querySelector('.braintree-form__hosted-field');
-
-  input.addEventListener('keyup', function () {
-    var valid = self._validateExtraInput(extraInput, true);
-
-    classList.toggle(nameContainer, 'braintree-form__field--valid', valid);
-
-    if (valid) {
-      self.hideFieldError(extraInput.fieldName);
-    }
-
-    self._sendRequestableEvent();
-  }, false);
-
-  if (extraInput.required) {
-    input.addEventListener('blur', function () {
-      // the active element inside the blur event is the document.body
-      // by taking it out of the event loop, we can detect the new
-      // active element (hosted field or other card view element)
-      setTimeout(function () {
-        if (isCardViewElement()) {
-          self._validateExtraInput(extraInput, true);
-        }
-      }, 0);
-    }, false);
-  }
-};
-
-CardView.prototype._removeExtraInput = function (extraInput) {
-  var field = this.getElementById(camelCaseToKebabCase(extraInput.fieldName) + '-field-group');
-
-  field.parentNode.removeChild(field);
-};
-
 CardView.prototype._sendRequestableEvent = function () {
   if (!this._isTokenizing) {
     this.model.setPaymentMethodRequestable({
@@ -174,6 +115,10 @@ CardView.prototype._generateHostedFieldsOptions = function () {
   var options = {
     client: this.client,
     fields: {
+      cardholderName: {
+        container: this._getFieldContainer('cardholder-name'),
+        placeholder: this.strings.cardholderNamePlaceholder
+      },
       number: {
         container: this._getFieldContainer('number'),
         placeholder: generateCardNumberPlaceholder()
@@ -217,6 +162,10 @@ CardView.prototype._generateHostedFieldsOptions = function () {
     }
   };
 
+  if (!this.hasCardholderName) {
+    delete options.fields.cardholderName;
+  }
+
   if (!hasCVVChallenge) {
     delete options.fields.cvv;
   }
@@ -293,10 +242,14 @@ CardView.prototype._validateForm = function (showFieldErrors) {
     }
 
     if (field.isEmpty) {
-      isValid = false;
+      if (!this.cardholderNameRequired && key === 'cardholderName') {
+        isValid = true;
+      } else {
+        isValid = false;
 
-      if (showFieldErrors) {
-        this.showFieldError(key, this.strings['fieldEmptyFor' + capitalize(key)]);
+        if (showFieldErrors) {
+          this.showFieldError(key, this.strings['fieldEmptyFor' + capitalize(key)]);
+        }
       }
     } else if (!field.isValid) {
       isValid = false;
@@ -321,50 +274,9 @@ CardView.prototype._validateForm = function (showFieldErrors) {
     }
   }
 
-  if (this.extraInputs) {
-    this.extraInputs.forEach(function (extraInput) {
-      var fieldIsValid;
-
-      if (!extraInput.enabled) {
-        return;
-      }
-
-      fieldIsValid = this._validateExtraInput(extraInput, showFieldErrors);
-
-      isValid = isValid && fieldIsValid;
-    }.bind(this));
-  }
-
   return isValid;
 };
 
-CardView.prototype._validateExtraInput = function (extraInput, showFieldError) {
-  var fieldNameKebab = camelCaseToKebabCase(extraInput.fieldName);
-  var field = this.getElementById(fieldNameKebab + '-field-group');
-  var input = field.querySelector('input');
-  var valid = true;
-
-  if (extraInput.required) {
-    valid = input.value.length > 0;
-
-    if (!valid && showFieldError) {
-      this.showFieldError(extraInput.fieldName, extraInput.requiredError);
-    }
-  }
-
-  extraInput.validations.forEach(function (validation) {
-    var validationPassed = validation.isValid(input.value);
-
-    if (!validationPassed && showFieldError) {
-      this.showFieldError(extraInput.fieldName, validation.error);
-    }
-
-    valid = valid && validationPassed;
-  }.bind(this));
-
-  return valid;
-};
-
 CardView.prototype.getPaymentMethod = function () { // eslint-disable-line consistent-return
   var formIsValid = this._validateForm();
 
@@ -392,8 +304,10 @@ CardView.prototype.tokenize = function () {
     return Promise.reject(new DropinError(constants.errors.NO_PAYMENT_METHOD_ERROR));
   }
 
-  if (this.hasCardholderName) {
-    tokenizeOptions.cardholderName = this.cardholderNameInput.value;
+  if (!this.cardholderNameRequired && state.fields.cardholderName && state.fields.cardholderName.isEmpty) {
+    tokenizeOptions.fieldsToTokenize = Object.keys(state.fields).filter(function (field) {
+      return field !== 'cardholderName';
+    });
   }
 
   self._isTokenizing = true;
@@ -405,10 +319,6 @@ CardView.prototype.tokenize = function () {
       Object.keys(state.fields).forEach(function (field) {
         self.hostedFieldsInstance.clear(field);
       });
-
-      if (self.hasCardholderName) {
-        self.cardholderNameInput.value = '';
-      }
     }
 
     if (self._shouldVault()) {
@@ -465,7 +375,7 @@ CardView.prototype.showFieldError = function (field, errorMessage) {
   fieldError = this.fieldErrors[field];
   fieldError.innerHTML = errorMessage;
 
-  if (input && isNormalFieldElement(input)) {
+  if (input) {
     input.setAttribute('aria-invalid', true);
   } else {
     this.hostedFieldsInstance.setAttribute({
@@ -490,7 +400,7 @@ CardView.prototype.hideFieldError = function (field) {
 
   classList.remove(fieldGroup, 'braintree-form__field-group--has-error');
 
-  if (input && isNormalFieldElement(input)) {
+  if (input) {
     input.removeAttribute('aria-invalid');
   } else {
     this.hostedFieldsInstance.removeAttribute({
@@ -528,7 +438,7 @@ CardView.prototype._onBlurEvent = function (event) {
 
   classList.remove(fieldGroup, 'braintree-form__field-group--is-focused');
 
-  if (shouldApplyFieldEmptyError(field)) {
+  if (this._shouldApplyFieldEmptyError(event.emittedBy, field)) {
     this.showFieldError(event.emittedBy, this.strings['fieldEmptyFor' + capitalize(event.emittedBy)]);
   } else if (!field.isEmpty && !field.isValid) {
     this.showFieldError(event.emittedBy, this.strings['fieldInvalidFor' + capitalize(event.emittedBy)]);
@@ -542,7 +452,7 @@ CardView.prototype._onBlurEvent = function (event) {
     // when focusing on a field by clicking the label,
     // we need to wait a bit for the iframe to be
     // focused properly before applying validations
-    if (shouldApplyFieldEmptyError(field)) {
+    if (this._shouldApplyFieldEmptyError(event.emittedBy, field)) {
       this.showFieldError(event.emittedBy, this.strings['fieldEmptyFor' + capitalize(event.emittedBy)]);
     }
   }.bind(this), 150);
@@ -634,13 +544,13 @@ CardView.prototype.onSelection = function () {
     return;
   }
 
-  if (this.hasCardholderName) {
-    setTimeout(function () { // wait until input is visible
-      this.cardholderNameInput.focus();
-    }.bind(this), 1);
-  } else {
-    this.hostedFieldsInstance.focus('number');
-  }
+  setTimeout(function () {
+    if (this.hasCardholderName) {
+      this.hostedFieldsInstance.focus('cardholderName');
+    } else {
+      this.hostedFieldsInstance.focus('number');
+    }
+  }.bind(this), 50);
 };
 
 CardView.prototype._hideUnsupportedCardIcons = function () {
@@ -671,19 +581,23 @@ CardView.isEnabled = function (options) {
   return Promise.resolve(!disabledByMerchant && gatewayConfiguration.creditCards.supportedCardTypes.length > 0);
 };
 
-function isNormalFieldElement(element) {
-  return element.id.indexOf('braintree__card-view-input') !== -1;
-}
+CardView.prototype._shouldApplyFieldEmptyError = function (fieldId, field) {
+  if (!field.isEmpty) {
+    return false;
+  }
 
-function shouldApplyFieldEmptyError(field) {
-  return field.isEmpty && isCardViewElement();
-}
+  if (fieldId === 'cardholderName' && !this.cardholderNameRequired) {
+    return false;
+  }
+
+  return isCardViewElement();
+};
 
 function isCardViewElement() {
   var activeId = document.activeElement && document.activeElement.id;
   var isHostedFieldsElement = document.activeElement instanceof HTMLIFrameElement && activeId.indexOf('braintree-hosted-field') !== -1;
 
-  return isHostedFieldsElement || isNormalFieldElement(document.activeElement);
+  return isHostedFieldsElement;
 }
 
 function camelCaseToKebabCase(string) {


Test patch:
diff --git a/test/unit/views/payment-sheet-views/card-view.js b/test/unit/views/payment-sheet-views/card-view.js
index a95ba83..c2d1ca1 100644
--- a/test/unit/views/payment-sheet-views/card-view.js
+++ b/test/unit/views/payment-sheet-views/card-view.js
@@ -1973,6 +1973,10 @@ describe('CardView', () => {
           fakeHostedFieldsInstance.getState.mockReturnValue({
             cards: [{ type: 'visa' }],
             fields: {
+              cardholderName: {
+                isEmpty: true,
+                isValid: false
+              },
               number: {
                 isValid: true
               },
@@ -2009,6 +2013,10 @@ describe('CardView', () => {
           fakeHostedFieldsInstance.getState.mockReturnValue({
             cards: [{ type: 'visa' }],
             fields: {
+              cardholderName: {
+                isEmpty: true,
+                isValid: false
+              },
               number: {
                 isValid: true
               },
@@ -2024,6 +2032,9 @@ describe('CardView', () => {
         }).then(() => {
           expect(cardViewWithCardholderName.model.reportError).not.toBeCalled();
           expect(fakeHostedFieldsInstance.tokenize).toBeCalledTimes(1);
+          expect(fakeHostedFieldsInstance.tokenize).toBeCalledWith(expect.objectContaining({
+            fieldsToTokenize: ['number', 'expirationDate']
+          }));
         });
       }
     );
@@ -2058,46 +2069,6 @@ describe('CardView', () => {
       });
     });
 
-    test(
-      'calls callback with error when cardholder name length is over 255 characters',
-      () => {
-        let cardViewWithCardholderName;
-
-        expect.assertions(3);
-
-        return makeCardView({
-          card: {
-            cardholderName: true
-          }
-        }).then((view) => {
-          cardViewWithCardholderName = view;
-        }).then(() => {
-          const overLengthValue = Array(257).join('a');
-
-          fakeHostedFieldsInstance.getState.mockReturnValue({
-            cards: [{ type: 'visa' }],
-            fields: {
-              number: {
-                isValid: true
-              },
-              expirationDate: {
-                isValid: true
-              }
-            }
-          });
-          jest.spyOn(cardViewWithCardholderName.model, 'reportError').mockImplementation();
-
-          cardViewWithCardholderName.cardholderNameInput.value = overLengthValue;
-
-          return cardViewWithCardholderName.tokenize().catch(err => {
-            expect(fakeHostedFieldsInstance.tokenize).not.toBeCalled();
-            expect(cardViewWithCardholderName.model.reportError).toBeCalledWith('hostedFieldsFieldsInvalidError');
-            expect(err.message).toBe('No payment method is available.');
-          });
-        });
-      }
-    );
-
     test(
       'reports an error to DropinModel when Hosted Fields tokenization returns an error',
       () => {
@@ -2481,6 +2452,21 @@ describe('CardView', () => {
       () => {
         let cardViewWithCardholderName;
 
+        fakeHostedFieldsInstance.getState.mockReturnValue({
+          cards: [{ type: 'visa' }],
+          fields: {
+            cardholderName: {
+              isValid: true
+            },
+            number: {
+              isValid: true
+            },
+            expirationDate: {
+              isValid: true
+            }
+          }
+        });
+
         return makeCardView({
           card: {
             cardholderName: true
@@ -2488,11 +2474,10 @@ describe('CardView', () => {
         }).then((view) => {
           cardViewWithCardholderName = view;
           fakeHostedFieldsInstance.tokenize.mockResolvedValue({ nonce: 'foo' });
-          cardViewWithCardholderName.cardholderNameInput.value = 'Some value';
 
           return cardViewWithCardholderName.tokenize();
         }).then(() => {
-          expect(cardViewWithCardholderName.cardholderNameInput.value).toBe('');
+          expect(fakeHostedFieldsInstance.clear).toBeCalledWith('cardholderName');
         });
       }
     );
@@ -2516,6 +2501,21 @@ describe('CardView', () => {
       () => {
         let cardViewWithCardholderName;
 
+        fakeHostedFieldsInstance.getState.mockReturnValue({
+          cards: [{ type: 'visa' }],
+          fields: {
+            cardholderName: {
+              isValid: true
+            },
+            number: {
+              isValid: true
+            },
+            expirationDate: {
+              isValid: true
+            }
+          }
+        });
+
         return makeCardView({
           card: {
             clearFieldsAfterTokenization: false,
@@ -2523,12 +2523,11 @@ describe('CardView', () => {
           }
         }).then((view) => {
           cardViewWithCardholderName = view;
-          cardViewWithCardholderName.cardholderNameInput.value = 'Some value';
           fakeHostedFieldsInstance.tokenize.mockResolvedValue({ nonce: 'foo' });
 
           return cardViewWithCardholderName.tokenize();
         }).then(() => {
-          expect(cardViewWithCardholderName.cardholderNameInput.value).toBe('Some value');
+          expect(fakeHostedFieldsInstance.clear).not.toBeCalled();
         });
       }
     );
@@ -2760,6 +2759,10 @@ describe('CardView', () => {
   });
 
   describe('onSelection', () => {
+    beforeEach(() => {
+      jest.useFakeTimers();
+    });
+
     test('focuses on the number field', () => {
       const view = new CardView({ element: cardElement });
 
@@ -2769,6 +2772,8 @@ describe('CardView', () => {
 
       view.onSelection();
 
+      jest.runAllTimers();
+
       expect(view.hostedFieldsInstance.focus).toBeCalledTimes(1);
       expect(view.hostedFieldsInstance.focus).toBeCalledWith('number');
     });
@@ -2780,6 +2785,8 @@ describe('CardView', () => {
 
       expect(() => {
         view.onSelection();
+
+        jest.runAllTimers();
       }).not.toThrowError();
     });
   });