diff --git a/app/assets/javascripts/app/form-field-format.js b/app/assets/javascripts/app/form-field-format.js index f00ec070241..b03ee377608 100644 --- a/app/assets/javascripts/app/form-field-format.js +++ b/app/assets/javascripts/app/form-field-format.js @@ -3,6 +3,7 @@ import DateFormatter from './modules/date-formatter'; import InternationalPhoneFormatter from './modules/international-phone-formatter'; import NumericFormatter from './modules/numeric-formatter'; import PersonalKeyFormatter from './modules/personal-key-formatter'; +import USPhoneFormatter from './modules/us-phone-formatter'; import ZipCodeFormatter from './modules/zip-code-formatter'; @@ -15,6 +16,7 @@ function formatForm() { ['.mfa', new NumericFormatter()], ['.mortgage', new NumericFormatter()], ['.phone', new InternationalPhoneFormatter()], + ['.us-phone', new USPhoneFormatter()], ['.personal-key', new PersonalKeyFormatter()], ['.ssn', new SocialSecurityNumberFormatter()], ['.zipcode', new ZipCodeFormatter()], diff --git a/app/assets/javascripts/app/modules/us-phone-formatter.js b/app/assets/javascripts/app/modules/us-phone-formatter.js new file mode 100644 index 00000000000..062cd827e11 --- /dev/null +++ b/app/assets/javascripts/app/modules/us-phone-formatter.js @@ -0,0 +1,16 @@ +import { PhoneFormatter } from 'field-kit'; + +class USPhoneFormatter extends PhoneFormatter { + isChangeValid(change, error) { + const match = change.proposed.text.match(/^\+(\d?)/); + if (match && match[1] === '') { + change.proposed.text = '+1'; + change.proposed.selectedRange.start = 4; + } else if (match && match[1] !== '1') { + return false; + } + return super.isChangeValid(change, error); + } +} + +export default USPhoneFormatter; diff --git a/app/forms/idv/phone_form.rb b/app/forms/idv/phone_form.rb index f59e0b4a6bf..dd490e0c0b0 100644 --- a/app/forms/idv/phone_form.rb +++ b/app/forms/idv/phone_form.rb @@ -6,20 +6,17 @@ class PhoneForm attr_reader :idv_params, :user, :phone attr_accessor :international_code + validate :phone_has_us_country_code + def initialize(idv_params, user) @idv_params = idv_params @user = user - self.phone = (idv_params[:phone] || user.phone).phony_formatted( - format: :international, normalize: :US, spaces: ' ' - ) + self.phone = initial_phone_value(idv_params[:phone] || user.phone) self.international_code = PhoneFormatter::DEFAULT_COUNTRY end def submit(params) - submitted_phone = params[:phone] - - formatted_phone = PhoneFormatter.new.format(submitted_phone, country_code: international_code) - + formatted_phone = PhoneFormatter.new.format(params[:phone]) self.phone = formatted_phone success = valid? @@ -32,6 +29,21 @@ def submit(params) attr_writer :phone + def initial_phone_value(phone) + formatted_phone = PhoneFormatter.new.format( + phone, country_code: PhoneFormatter::DEFAULT_COUNTRY + ) + return unless Phony.plausible? formatted_phone + self.phone = formatted_phone + end + + def phone_has_us_country_code + country_code = Phonelib.parse(phone).country_code || '1' + return if country_code == '1' + + errors.add(:phone, :must_have_us_country_code) + end + def update_idv_params(phone) normalized_phone = phone.gsub(/\D/, '')[1..-1] idv_params[:phone] = normalized_phone diff --git a/app/views/verify/phone/new.html.slim b/app/views/verify/phone/new.html.slim index fc594dfa05c..39dd10e522d 100644 --- a/app/views/verify/phone/new.html.slim +++ b/app/views/verify/phone/new.html.slim @@ -12,6 +12,8 @@ p.mt-tiny.mb2 = t('idv.messages.phone.intro') = t('idv.messages.phone.in_your_name') li = t('idv.messages.phone.prepaid') + li + = t('idv.messages.phone.us_country_code') em = t('idv.messages.phone.same_as_2fa') @@ -22,7 +24,7 @@ em span.ml1 em = t('idv.form.phone_label_aside') - = f.input :phone, required: true, input_html: { class: 'phone' }, label: false, + = f.input :phone, required: true, input_html: { class: 'us-phone' }, label: false, wrapper_html: { class: 'inline-block mr2' } = f.button :submit, t('forms.buttons.continue') diff --git a/config/locales/errors/en.yml b/config/locales/errors/en.yml index 38a86bf6719..13d14c83684 100644 --- a/config/locales/errors/en.yml +++ b/config/locales/errors/en.yml @@ -19,6 +19,8 @@ en: improbable_phone: Invalid phone number. Please make sure you enter a valid phone number. missing_field: Please fill in this field. + must_have_us_country_code: Invalid phone number. Please make sure you enter + a phone number with a U.S. country code. no_password_reset_profile: No profile has been recently deactivated due to a password reset no_pending_profile: No profile is waiting for verification diff --git a/config/locales/errors/es.yml b/config/locales/errors/es.yml index bdac55909c4..aaf5b701dfa 100644 --- a/config/locales/errors/es.yml +++ b/config/locales/errors/es.yml @@ -18,6 +18,7 @@ es: format_mismatch: Por favor, use el formato solicitado. improbable_phone: NOT TRANSLATED YET missing_field: Por favor, rellene este campo. + must_have_us_country_code: NOT TRANSLATED YET no_password_reset_profile: Ningún perfil ha sido desactivado recientemente por un restablecimiento de contraseña. no_pending_profile: Ningún perfil está esperando verificación diff --git a/config/locales/errors/fr.yml b/config/locales/errors/fr.yml index 798dbd4f92c..5ce5df6cfc5 100644 --- a/config/locales/errors/fr.yml +++ b/config/locales/errors/fr.yml @@ -21,6 +21,7 @@ fr: format_mismatch: Veuillez vous assurer de respecter le format requis. improbable_phone: NOT TRANSLATED YET missing_field: Veuillez remplir ce champ. + must_have_us_country_code: NOT TRANSLATED YET no_password_reset_profile: Aucun profil récemment désactivé en raison d'une réinitialisation de mot de passe no_pending_profile: Aucun profil en attente de vérification diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index d9c90b62222..b413ebc3505 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -138,6 +138,7 @@ en: prepaid: on a contract, not prepaid same_as_2fa: This phone number can be the same one you used to set up your one-time password as long as it meets the criteria above. + us_country_code: have a U.S. country code to accept phone calls review: financial_info: Where is my financial account information? info_verified_html: We found records matching your %{phone_message} diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index 310c54b873c..b21a8e6f7a4 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -141,6 +141,7 @@ es: same_as_2fa: Este número de teléfono puede ser el mismo que utilizó para configurar su contraseña de un uso único, siempre y cuando cumpla con los criterios anteriores. + us_country_code: NOT TRANSLATED YET review: financial_info: "¿Dónde está la información de mi cuenta financiera?" info_verified_html: Encontramos registros que coinciden con su %{teléfono_mensaje} diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index 4ff911154a1..923da86d2c8 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -150,6 +150,7 @@ fr: same_as_2fa: Ce numéro de téléphone peut être le même que celui que vous utilisez pour configurer votre mot de passe à usage unique, tant et aussi longtemps qu'il respecte les critères mentionnés plus haut. + us_country_code: NOT TRANSLATED YET review: financial_info: Où se trouve l'information sur mon compte bancaire? info_verified_html: Nous avons trouvé des données qui correspondent à votre diff --git a/spec/controllers/verify/phone_controller_spec.rb b/spec/controllers/verify/phone_controller_spec.rb index 93d9c65f9f9..077ee3e5167 100644 --- a/spec/controllers/verify/phone_controller_spec.rb +++ b/spec/controllers/verify/phone_controller_spec.rb @@ -83,7 +83,7 @@ it 'tracks form error and does not make a vendor API call' do expect(Idv::PhoneValidator).to_not receive(:new) - put :create, idv_phone_form: { phone: '703', international_code: 'US' } + put :create, idv_phone_form: { phone: '703' } result = { success: false, diff --git a/spec/features/idv/phone_spec.rb b/spec/features/idv/phone_spec.rb index f01a199687f..b8423493066 100644 --- a/spec/features/idv/phone_spec.rb +++ b/spec/features/idv/phone_spec.rb @@ -83,7 +83,22 @@ fill_in 'Phone', with: '' find('#idv_phone_form_phone').native.send_keys('abcd1234') - expect(find('#idv_phone_form_phone').value).to eq '+1 234' + expect(find('#idv_phone_form_phone').value).to eq '1 (234) ' + end + + scenario 'phone field does not format international numbers', :js, idv_job: true do + sign_in_and_2fa_user + visit verify_session_path + fill_out_idv_form_ok + click_idv_continue + fill_out_financial_form_ok + click_idv_continue + + visit verify_phone_path + fill_in 'Phone', with: '' + find('#idv_phone_form_phone').native.send_keys('+81543543643') + + expect(find('#idv_phone_form_phone').value).to eq '+1 (815) 435-4364' end def complete_idv_profile_with_phone(phone) diff --git a/spec/forms/idv/phone_form_spec.rb b/spec/forms/idv/phone_form_spec.rb index 6f08a1630e2..001096d9b56 100644 --- a/spec/forms/idv/phone_form_spec.rb +++ b/spec/forms/idv/phone_form_spec.rb @@ -49,5 +49,26 @@ expect(subject.idv_params).to eq expected_params end + + it 'uses the user phone number as the initial phone value' do + user = build_stubbed(:user, :signed_up, phone: '555-555-1234') + subject = Idv::PhoneForm.new({}, user) + + expect(subject.phone).to eq('+1 (555) 555-1234') + end + + it 'does not use an international number as the initial phone value' do + user = build_stubbed(:user, :signed_up, phone: '+81 54 354 3643') + subject = Idv::PhoneForm.new({}, user) + + expect(subject.phone).to eq(nil) + end + + it 'does not allow numbers with a non-US country code' do + result = subject.submit(phone: '+81 54 354 3643') + + expect(result.success?).to eq(false) + expect(result.errors[:phone]).to include(t('errors.messages.must_have_us_country_code')) + end end end