Skip to content

Commit

Permalink
Merge pull request #5940 from Expensify/joe-update-add-debit-form
Browse files Browse the repository at this point in the history
Update Add Debit Card form to match VBA styles
  • Loading branch information
marcaaron authored Oct 27, 2021
2 parents 778500b + e2897a5 commit ed2d251
Show file tree
Hide file tree
Showing 15 changed files with 352 additions and 201 deletions.
3 changes: 2 additions & 1 deletion src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ const CONST = {
KEYBOARD_TYPE: {
NUMERIC: 'numeric',
PHONE_PAD: 'phone-pad',
NUMBER_PAD: 'number-pad',
},

ATTACHMENT_PICKER_TYPE: {
Expand Down Expand Up @@ -443,7 +444,7 @@ const CONST = {
NUMBER: /^[0-9]+$/,
CARD_NUMBER: /^[0-9]{15,16}$/,
CARD_SECURITY_CODE: /^[0-9]{3,4}$/,
CARD_EXPIRATION_DATE: /(0[1-9]|10|11|12)\/20[0-9]{2}$/,
CARD_EXPIRATION_DATE: /^(0[1-9]|1[0-2])([^0-9])?([0-9]{4}|([0-9]{2}))$/,
PAYPAL_ME_USERNAME: /^[a-zA-Z0-9]+$/,

// Adapted from: https://gist.github.com/dperini/729294
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,7 @@ export default {

// Set when we are loading payment methods
IS_LOADING_PAYMENT_METHODS: 'isLoadingPaymentMethods',

// Stores values for the add debit card form
ADD_DEBIT_CARD_FORM: 'addDebitCardForm',
};
6 changes: 6 additions & 0 deletions src/components/FormAlertWithSubmitButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const propTypes = {
/** Styles for container element */
containerStyles: PropTypes.arrayOf(PropTypes.object),

/** Is the button in a loading state */
isLoading: PropTypes.bool,

...withLocalizePropTypes,
};

Expand All @@ -45,6 +48,7 @@ const defaultProps = {
isDisabled: false,
isMessageHtml: false,
containerStyles: [],
isLoading: false,
};

const FormAlertWithSubmitButton = ({
Expand All @@ -57,6 +61,7 @@ const FormAlertWithSubmitButton = ({
message,
isMessageHtml,
containerStyles,
isLoading,
}) => {
/**
* @returns {React.Component}
Expand Down Expand Up @@ -114,6 +119,7 @@ const FormAlertWithSubmitButton = ({
text={buttonText}
onPress={onSubmit}
isDisabled={isDisabled}
isLoading={isLoading}
/>
</View>
);
Expand Down
25 changes: 12 additions & 13 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export default {
confirm: 'Confirm',
reset: 'Reset',
done: 'Done',
debitCard: 'Debit card',
payPalMe: 'PayPal.me/',
},
attachmentPicker: {
cameraPermissionRequired: 'Camera permission required',
Expand Down Expand Up @@ -279,32 +281,29 @@ export default {
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Enter your username to get paid back via PayPal.',
payPalMe: 'PayPal.me/',
yourPayPalUsername: 'Your PayPal username',
addPayPalAccount: 'Add PayPal account',
editPayPalAccount: 'Update PayPal account',
growlMessageOnSave: 'Your PayPal username was successfully added',
formatError: 'Invalid PayPal.me username',
},
addDebitCardPage: {
addADebitCard: 'Add a Debit Card',
nameOnCard: 'Name on Card',
debitCardNumber: 'Debit Card Number',
expiration: 'Expiration',
expirationDate: 'MM/YYYY',
addADebitCard: 'Add a debit card',
nameOnCard: 'Name on card',
debitCardNumber: 'Debit card number',
expiration: 'Expiration date',
expirationDate: 'MM/YY',
cvv: 'CVV',
billingAddress: 'Billing Address',
streetAddress: 'Street Address',
cityName: 'City Name',
expensifyTermsOfService: 'Expensify Terms Of Service',
billingAddress: 'Billing address',
expensifyTermsOfService: 'Expensify Terms of Service',
growlMessageOnSave: 'Your debit card was successfully added',
error: {
invalidName: 'Please add a valid name',
zipCode: 'Please enter a valid zip code',
invalidName: 'Please enter a valid name',
addressZipCode: 'Please enter a valid zip code',
debitCardNumber: 'Please enter a valid debit card number',
expirationDate: 'Please enter a valid expiration date',
securityCode: 'Please enter a valid security code',
address: 'Please enter a valid billing address',
addressStreet: 'Please enter a valid billing address that is not a PO Box',
addressState: 'Please select a state',
addressCity: 'Please enter a city',
acceptedTerms: 'You must accept the Terms of Service to continue',
Expand Down
15 changes: 7 additions & 8 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export default {
confirm: 'Confirmar',
reset: 'Restablecer',
done: 'Listo',
debitCard: 'Tarjeta de débito',
payPalMe: 'PayPal.me/',
},
attachmentPicker: {
cameraPermissionRequired: 'Se necesita permiso para usar la cámara',
Expand Down Expand Up @@ -279,7 +281,6 @@ export default {
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.',
payPalMe: 'PayPal.me/',
yourPayPalUsername: 'Tu usuario de PayPal',
addPayPalAccount: 'Agregar cuenta de PayPal',
growlMessageOnSave: 'Su nombre de usuario de PayPal se agregó correctamente',
Expand All @@ -290,21 +291,19 @@ export default {
addADebitCard: 'Agregar una tarjeta de débito',
nameOnCard: 'Nombre en la tarjeta',
debitCardNumber: 'Numero de la tarjeta de débito',
expiration: 'Vencimiento',
expiration: 'Fecha de vencimiento',
expirationDate: 'MM/AA',
cvv: 'CVV',
billingAddress: 'Dirección de Envio',
streetAddress: 'Dirección',
cityName: 'Nombre de la ciudad',
billingAddress: 'Dirección de envio',
expensifyTermsOfService: 'Expensify Términos de servicio',
growlMessageOnSave: 'Su tarteja de débito se agregó correctamente',
error: {
invalidName: 'Por favor agregue un nombre válido',
zipCode: 'Por favor ingrese un código postal válido',
invalidName: 'Por favor ingrese un nombre válido',
addressZipCode: 'Por favor ingrese un código postal válido',
debitCardNumber: 'Ingrese un número de tarjeta de débito válido',
expirationDate: 'Por favor introduzca una fecha de vencimiento válida',
securityCode: 'Ingrese un código de seguridad válido',
address: 'Ingrese una dirección de facturación válida',
addressStreet: 'Ingrese una dirección de facturación válida que no sea un apartado postal',
addressState: 'Por favor seleccione un estado',
addressCity: 'Por favor ingrese una ciudad',
acceptedTerms: 'Debes aceptar los Términos de servicio para continuar',
Expand Down
39 changes: 39 additions & 0 deletions src/libs/CardUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Returns the masked card number (ex: 4242XXXXXXXX4242)
*
* @param {String} cardNumber
* @return {Boolean}
*/
function maskCardNumber(cardNumber) {
const firstFour = cardNumber.substring(0, 4);
const lastFour = cardNumber.substring(cardNumber.length - 4);

return `${firstFour}${'X'.repeat(cardNumber.length - 8)}${lastFour}`;
}

/**
* @param {String} expirationDateString - string in MM/YYYY, MM/YY, MMYY, or MMYYYY format
* @returns {String}
*/
function getMonthFromExpirationDateString(expirationDateString) {
return expirationDateString.substr(0, 2);
}

/**
* @param {String} expirationDateString - string in MMYY or MMYYYY format, with any non-number separator
* @returns {String}
*/
function getYearFromExpirationDateString(expirationDateString) {
const stringContainsNumbersOnly = /^\d+$/.test(expirationDateString);
const cardYear = stringContainsNumbersOnly
? expirationDateString.substr(2)
: expirationDateString.substr(3);

return cardYear.length === 2 ? `20${cardYear}` : cardYear;
}

export {
maskCardNumber,
getMonthFromExpirationDateString,
getYearFromExpirationDateString,
};
17 changes: 13 additions & 4 deletions src/libs/ValidationUtils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import moment from 'moment';
import _ from 'underscore';
import CONST from '../CONST';

import {getMonthFromExpirationDateString, getYearFromExpirationDateString} from './CardUtils';

/**
* Implements the Luhn Algorithm, a checksum formula used to validate credit card
Expand Down Expand Up @@ -76,14 +76,23 @@ function isRequiredFulfilled(value) {
}

/**
* Validates that this is a valid expiration date
* in the MM/YY or MM/YYYY format
* Validates that this is a valid expiration date. Supports the following formats:
* 1. MM/YY
* 2. MM/YYYY
* 3. MMYY
* 4. MMYYYY
*
* @param {String} string
* @returns {Boolean}
*/
function isValidExpirationDate(string) {
return CONST.REGEX.CARD_EXPIRATION_DATE.test(string);
if (!CONST.REGEX.CARD_EXPIRATION_DATE.test(string)) {
return false;
}

// Use the last of the month to check if the expiration date is in the future or not
const expirationDate = `${getYearFromExpirationDateString(string)}-${getMonthFromExpirationDateString(string)}-01`;
return moment(expirationDate).endOf('month').isAfter(moment());
}

/**
Expand Down
34 changes: 26 additions & 8 deletions src/libs/actions/PaymentMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ROUTES from '../../ROUTES';
import Growl from '../Growl';
import {translateLocal} from '../translate';
import Navigation from '../Navigation/Navigation';
import {maskCardNumber} from '../cardUtils';
import {maskCardNumber, getMonthFromExpirationDateString, getYearFromExpirationDateString} from '../CardUtils';

/**
* Calls the API to get the user's bankAccountList, cardList, wallet, and payPalMe
Expand Down Expand Up @@ -41,28 +41,30 @@ function getPaymentMethods() {
* @param {Object} params
*/
function addBillingCard(params) {
const cardYear = params.expirationDate.substr(3);
const cardMonth = params.expirationDate.substr(0, 2);
const cardMonth = getMonthFromExpirationDateString(params.expirationDate);
const cardYear = getYearFromExpirationDateString(params.expirationDate);

Onyx.merge(ONYXKEYS.ADD_DEBIT_CARD_FORM, {submitting: true});
API.AddBillingCard({
cardNumber: params.cardNumber,
cardYear,
cardMonth,
cardCVV: params.securityCode,
addressName: params.nameOnCard,
addressZip: params.zipCode,
addressZip: params.addressZipCode,
currency: CONST.CURRENCY.USD,
}).then(((response) => {
let errorMessage = '';
if (response.jsonCode === 200) {
const cardObject = {
additionalData: {
isBillingCard: false,
isP2PDebitCard: true,
},
addressName: params.nameOnCard,
addressState: params.selectedState,
addressStreet: params.billingAddress,
addressZip: params.zipCode,
addressState: params.addressState,
addressStreet: params.addressStreet,
addressZip: params.addressZipCode,
cardMonth,
cardNumber: maskCardNumber(params.cardNumber),
cardYear,
Expand All @@ -73,12 +75,28 @@ function addBillingCard(params) {
Growl.show(translateLocal('addDebitCardPage.growlMessageOnSave'), CONST.GROWL.SUCCESS, 3000);
Navigation.navigate(ROUTES.SETTINGS_PAYMENTS);
} else {
Growl.error(translateLocal('addDebitCardPage.error.genericFailureMessage', 3000));
errorMessage = response.message ? response.message : translateLocal('addDebitCardPage.error.genericFailureMessage');
}

Onyx.merge(ONYXKEYS.ADD_DEBIT_CARD_FORM, {
submitting: false,
error: errorMessage,
});
}));
}

/**
* Resets the values for the add debit card form back to their initial states
*/
function clearDebitCardFormErrorAndSubmit() {
Onyx.set(ONYXKEYS.ADD_DEBIT_CARD_FORM, {
submitting: false,
error: '',
});
}

export {
getPaymentMethods,
addBillingCard,
clearDebitCardFormErrorAndSubmit,
};
17 changes: 0 additions & 17 deletions src/libs/cardUtils.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/EnablePayments/AdditionalDetailsStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class AdditionalDetailsStep extends React.Component {
label: props.translate('common.ssnLast4'),
fieldName: 'ssn',
maxLength: 4,
keyboardType: 'number-pad',
keyboardType: CONST.KEYBOARD_TYPE.NUMBER_PAD,
},
];

Expand Down
4 changes: 2 additions & 2 deletions src/pages/ReimbursementAccount/BankAccountStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class BankAccountStep extends React.Component {
/>
<ExpensiTextInput
label={this.props.translate('bankAccount.routingNumber')}
keyboardType="number-pad"
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
value={this.state.routingNumber}
onChangeText={value => this.clearErrorAndSetValue('routingNumber', value)}
disabled={shouldDisableInputs}
Expand All @@ -272,7 +272,7 @@ class BankAccountStep extends React.Component {
<ExpensiTextInput
containerStyles={[styles.mt4]}
label={this.props.translate('bankAccount.accountNumber')}
keyboardType="number-pad"
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
value={this.state.accountNumber}
onChangeText={value => this.clearErrorAndSetValue('accountNumber', value)}
disabled={shouldDisableInputs}
Expand Down
Loading

0 comments on commit ed2d251

Please sign in to comment.