UNPKG

react-credit-cards

Version:
259 lines (225 loc) 6.86 kB
import React from 'react'; import PropTypes from 'prop-types'; import Payment from 'payment'; class ReactCreditCards extends React.Component { constructor(props) { super(props); this.setCards(); } static propTypes = { acceptedCards: PropTypes.array, callback: PropTypes.func, cvc: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]).isRequired, expiry: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]).isRequired, focused: PropTypes.string, issuer: PropTypes.string, locale: PropTypes.shape({ valid: PropTypes.string, }), name: PropTypes.string.isRequired, number: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]).isRequired, placeholders: PropTypes.shape({ name: PropTypes.string, }), preview: PropTypes.bool, }; static defaultProps = { acceptedCards: [], locale: { valid: 'valid thru', }, placeholders: { name: 'YOUR NAME HERE', }, preview: false, }; componentDidUpdate(prevProps) { const { acceptedCards, callback, number } = this.props; if (prevProps.number !== number) { /* istanbul ignore else */ if (typeof callback === 'function') { callback(this.options, Payment.fns.validateCardNumber(number)); } } if (prevProps.acceptedCards.toString() !== acceptedCards.toString()) { this.setCards(); } } get issuer() { const { issuer, preview } = this.props; return preview && issuer ? issuer.toLowerCase() : this.options.issuer; } get number() { const { number, preview } = this.props; let maxLength = preview ? 19 : this.options.maxLength; let nextNumber = typeof number === 'number' ? number.toString() : number.replace(/[A-Za-z]| /g, ''); if (isNaN(parseInt(nextNumber, 10)) && !preview) { nextNumber = ''; } if (maxLength > 16) { maxLength = nextNumber.length <= 16 ? 16 : maxLength; } if (nextNumber.length > maxLength) { nextNumber = nextNumber.slice(0, maxLength); } while (nextNumber.length < maxLength) { nextNumber += '•'; } if (['amex', 'dinersclub'].includes(this.issuer)) { const format = [0, 4, 10]; const limit = [4, 6, 5]; nextNumber = `${nextNumber.substr(format[0], limit[0])} ${nextNumber.substr(format[1], limit[1])} ${nextNumber.substr(format[2], limit[2])}`; } else if (nextNumber.length > 16) { const format = [0, 4, 8, 12]; const limit = [4, 7]; nextNumber = `${nextNumber.substr(format[0], limit[0])} ${nextNumber.substr(format[1], limit[0])} ${nextNumber.substr(format[2], limit[0])} ${nextNumber.substr(format[3], limit[1])}`; } else { for (let i = 1; i < (maxLength / 4); i++) { const space_index = (i * 4) + (i - 1); nextNumber = `${nextNumber.slice(0, space_index)} ${nextNumber.slice(space_index)}`; } } return nextNumber; } get expiry() { const { expiry = '' } = this.props; const date = typeof expiry === 'number' ? expiry.toString() : expiry; let month = ''; let year = ''; if (date.includes('/')) { [month, year] = date.split('/'); } else if (date.length) { month = date.substr(0, 2); year = date.substr(2, 6); } while (month.length < 2) { month += '•'; } if (year.length > 2) { year = year.substr(2, 4); } while (year.length < 2) { year += '•'; } return `${month}/${year}`; } get options() { const { number } = this.props; const issuer = Payment.fns.cardType(number) || 'unknown'; let maxLength = 16; if (issuer === 'amex') { maxLength = 15; } else if (issuer === 'dinersclub') { maxLength = 14; } else if (['hipercard', 'mastercard', 'visa'].includes(issuer)) { maxLength = 19; } return { issuer, maxLength, }; } setCards() { const { acceptedCards } = this.props; let newCardArray = []; if (acceptedCards.length) { Payment.getCardArray() .forEach(d => { if (acceptedCards.includes(d.type)) { newCardArray.push(d); } }); } else { newCardArray = newCardArray.concat(Payment.getCardArray()); } Payment.setCardArray(newCardArray); } render() { const { cvc, focused, locale, name, placeholders } = this.props; const { number, expiry } = this; return ( <div key="Cards" className="rccs"> <div className={[ 'rccs__card', `rccs__card--${this.issuer}`, focused === 'cvc' && this.issuer !== 'amex' ? 'rccs__card--flipped' : '', ].join(' ').trim()} > <div className="rccs__card--front"> <div className="rccs__card__background" /> <div className="rccs__issuer" /> <div className={[ 'rccs__cvc__front', focused === 'cvc' ? 'rccs--focused' : '', ].join(' ').trim()} > {cvc} </div> <div className={[ 'rccs__number', number.replace(/ /g, '').length > 16 ? 'rccs__number--large' : '', focused === 'number' ? 'rccs--focused' : '', number.substr(0, 1) !== '•' ? 'rccs--filled' : '', ].join(' ').trim()} > {number} </div> <div className={[ 'rccs__name', focused === 'name' ? 'rccs--focused' : '', name ? 'rccs--filled' : '', ].join(' ').trim()} > {name || placeholders.name} </div> <div className={[ 'rccs__expiry', focused === 'expiry' ? 'rccs--focused' : '', expiry.substr(0, 1) !== '•' ? 'rccs--filled' : '', ].join(' ').trim()} > <div className="rccs__expiry__valid">{locale.valid}</div> <div className="rccs__expiry__value">{expiry}</div> </div> <div className="rccs__chip" /> </div> <div className="rccs__card--back"> <div className="rccs__card__background" /> <div className="rccs__stripe" /> <div className="rccs__signature" /> <div className={[ 'rccs__cvc', focused === 'cvc' ? 'rccs--focused' : '', ].join(' ').trim()} > {cvc} </div> <div className="rccs__issuer" /> </div> </div> </div> ); } } export default ReactCreditCards;