UNPKG

angular-cc-library

Version:
687 lines (677 loc) 24 kB
import * as i1 from '@angular/forms'; import { Validators } from '@angular/forms'; import * as i0 from '@angular/core'; import { Directive, Self, Optional, HostListener, NgModule } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; const defaultFormat = /(\d{1,4})/g; const cards = [ { type: 'maestro', patterns: [5018, 502, 503, 506, 56, 58, 639, 6220, 67], format: defaultFormat, length: [12, 13, 14, 15, 16, 17, 18, 19], cvvLength: [3], luhn: true, }, { type: 'forbrugsforeningen', patterns: [600], format: defaultFormat, length: [16], cvvLength: [3], luhn: true, }, { type: 'dankort', patterns: [5019], format: defaultFormat, length: [16], cvvLength: [3], luhn: true, }, { type: 'visa', patterns: [4], format: defaultFormat, length: [13, 16, 19], cvvLength: [3], luhn: true, }, { type: 'mastercard', patterns: [51, 52, 53, 54, 55, 22, 23, 24, 25, 26, 27], format: defaultFormat, length: [16], cvvLength: [3], luhn: true, }, { type: 'amex', patterns: [34, 37], format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/, length: [15], cvvLength: [3, 4], luhn: true, }, { type: 'dinersclub', patterns: [30, 36, 38, 39], format: /(\d{1,4})(\d{1,6})?(\d{1,4})?/, length: [14], cvvLength: [3], luhn: true, }, { type: 'discover', patterns: [60, 64, 65, 622], format: defaultFormat, length: [16], cvvLength: [3], luhn: true, }, { type: 'unionpay', patterns: [62, 88], format: defaultFormat, length: [16, 17, 18, 19], cvvLength: [3], luhn: false, }, { type: 'jcb', patterns: [35], format: defaultFormat, length: [16, 19], cvvLength: [3], luhn: true, }, ]; // @dynamic class CreditCard { static cards() { return cards; } static cardFromNumber(num) { num = (num + '').replace(/\D/g, ''); for (let i = 0, len = cards.length; i < len; i++) { const card = cards[i]; const ref = card.patterns; for (let j = 0, len1 = ref.length; j < len1; j++) { const pattern = ref[j]; const p = pattern + ''; if (num.substr(0, p.length) === p) { return card; } } } } static restrictNumeric(e) { if (e.metaKey || e.ctrlKey) { return true; } if (e.which === 32) { return false; } if (e.which === 0) { return true; } if (e.which < 33) { return true; } const input = String.fromCharCode(e.which); return !!/[\d\s]/.test(input); } static hasTextSelected(target) { return target.selectionStart !== null && target.selectionStart !== target.selectionEnd; } static cardType(num) { if (!num) { return num; } const card = this.cardFromNumber(num); if (card !== null && typeof card !== 'undefined') { return card.type; } else { return null; } } static formatCardNumber(num) { num = num.replace(/\D/g, ''); const card = this.cardFromNumber(num); if (!card) { return num; } const upperLength = card.length[card.length.length - 1]; num = num.slice(0, upperLength); if (card.format.global) { const matches = num.match(card.format); if (matches != null) { return matches.join(' '); } } else { const groups = card.format.exec(num); if (groups == null) { return; } groups.shift(); return groups.filter(Boolean).join(' '); } } static safeVal(value, target, updateValue) { let cursor = null; const last = target.value; let result = null; try { cursor = target.selectionStart; } catch (error) { // do nothing } updateValue(value); if (cursor !== null && target === document.activeElement) { if (cursor === last.length) { cursor = value.length; } if (last !== value) { const prevPair = last.slice(cursor - 1, +cursor + 1 || 9e9); const currPair = value.slice(cursor - 1, +cursor + 1 || 9e9); const digit = value[cursor]; if (/\d/.test(digit) && prevPair === (`${digit} `) && currPair === (` ${digit}`)) { cursor = cursor + 1; } } result = cursor; } return result; } static isCardNumber(key, target) { const digit = String.fromCharCode(key); if (!/^\d+$/.test(digit)) { return false; } if (CreditCard.hasTextSelected(target)) { return true; } const value = (target.value + digit).replace(/\D/g, ''); const card = CreditCard.cardFromNumber(value); if (card) { return value.length <= card.length[card.length.length - 1]; } else { return value.length <= 16; } } static restrictExpiry(key, target) { const digit = String.fromCharCode(key); if (!/^\d+$/.test(digit) || this.hasTextSelected(target)) { return false; } const value = `${target.value}${digit}`.replace(/\D/g, ''); return value.length > 6; } static replaceFullWidthChars(str) { if (str === null) { str = ''; } const fullWidth = '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19'; const halfWidth = '0123456789'; let value = ''; const chars = str.split(''); for (let i = 0; i < chars.length; i++) { let chr = chars[i]; const idx = fullWidth.indexOf(chr); if (idx > -1) { chr = halfWidth[idx]; } value += chr; } return value; } static formatExpiry(expiry) { const parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/); if (!parts) { return ''; } let mon = parts[1] || ''; let sep = parts[2] || ''; const year = parts[3] || ''; if (year.length > 0) { sep = ' / '; } else if (sep === ' /') { mon = mon.substring(0, 1); sep = ''; } else if (mon.length === 2 || sep.length > 0) { sep = ' / '; } else if (mon.length === 1 && (mon !== '0' && mon !== '1')) { mon = `0${mon}`; sep = ' / '; } return `${mon}${sep}${year}`; } static restrictCvc(key, target) { const digit = String.fromCharCode(key); if (!/^\d+$/.test(digit) || this.hasTextSelected(target)) { return false; } const val = `${target.value}${digit}`; return val.length <= 4; } static luhnCheck(num) { const digits = num.split('').reverse(); let odd = true; let sum = 0; for (let i = 0; i < digits.length; i++) { let digit = parseInt(digits[i], 10); if ((odd = !odd)) { digit *= 2; } if (digit > 9) { digit -= 9; } sum += digit; } return sum % 10 === 0; } } class CreditCardValidators { static validateCCNumber(control) { if (Validators.required(control) !== undefined && Validators.required(control) !== null) { return { ccNumber: true }; } const num = control.value.toString().replace(/\s+|-/g, ''); if (!/^\d+$/.test(num)) { return { ccNumber: true }; } const card = CreditCard.cardFromNumber(num); if (!card) { return { ccNumber: true }; } if (card.length.includes(num.length) && (card.luhn === false || CreditCard.luhnCheck(num))) { return null; } const upperlength = card.length[card.length.length - 1]; if (num.length > upperlength) { const registeredNum = num.substring(0, upperlength); if (CreditCard.luhnCheck(registeredNum)) { return null; } } return { ccNumber: true }; } static validateExpDate(control) { if (Validators.required(control) !== undefined && Validators.required(control) !== null) { return { expDate: true }; } if (typeof control.value !== 'undefined' && control.value.length >= 5) { let [month, year] = control.value.split(/[\s/]+/, 2); if ((year != null ? year.length : void 0) === 2 && /^\d+$/.test(year)) { const prefix = new Date().getFullYear().toString().slice(0, 2); year = prefix + year; } month = parseInt(month, 10).toString(); year = parseInt(year, 10).toString(); if (/^\d+$/.test(month) && /^\d+$/.test(year) && (month >= 1 && month <= 12)) { const expiry = new Date(year, month); const currentTime = new Date(); expiry.setMonth(expiry.getMonth() - 1); expiry.setMonth(expiry.getMonth() + 1, 1); if (expiry > currentTime) { return null; } } } return { expDate: true }; } } class CreditCardFormatDirective { constructor(el, control) { this.el = el; this.control = control; this.cards = CreditCard.cards(); this.resolvedScheme$ = new BehaviorSubject('unknown'); this.target = this.el.nativeElement; } /** * Updates the value to target element, or FormControl if exists. * @param value New input value. */ updateValue(value) { if (this.control) { this.control.control.setValue(value); } else { this.target.value = value; } } onKeypress(e) { if (CreditCard.restrictNumeric(e)) { if (CreditCard.isCardNumber(e.which, this.target)) { this.formatCardNumber(e); } } else { e.preventDefault(); } } onKeydown(e) { this.formatBackCardNumber(e); this.reFormatCardNumber(); } onKeyup() { this.setCardType(); } onPaste() { this.reFormatCardNumber(); } onChange() { this.reFormatCardNumber(); } onInput() { this.reFormatCardNumber(); this.setCardType(); } formatCardNumber(e) { const digit = String.fromCharCode(e.which); if (!/^\d+$/.test(digit)) { return; } const value = this.target.value; const card = CreditCard.cardFromNumber(value + digit); const length = (value.replace(/\D/g, '') + digit).length; const upperLength = card ? card.length[card.length.length - 1] : 19; if (length >= upperLength) { return; } } formatBackCardNumber(e) { const value = this.target.value; const selStart = this.target.selectionStart; if (e.which !== 8) { return; } if (selStart != null && selStart === this.target.selectionEnd && selStart > 0 && selStart !== value.length && value[selStart - 1] === ' ') { e.preventDefault(); if (selStart <= 2) { this.updateValue(value.slice(selStart)); this.target.selectionStart = 0; this.target.selectionEnd = 0; } else { this.updateValue(value.slice(0, selStart - 2) + value.slice(selStart)); this.target.selectionStart = selStart - 2; this.target.selectionEnd = selStart - 2; } } } setCardType() { const cardType = CreditCard.cardType(this.target.value) || 'unknown'; this.resolvedScheme$.next(cardType); if (!this.target.classList.contains(cardType)) { this.cards.forEach((card) => { this.target.classList.remove(card.type); }); this.target.classList.remove('unknown'); this.target.classList.add(cardType); this.target.classList.toggle('identified', cardType !== 'unknown'); } } reFormatCardNumber() { const value = CreditCard.formatCardNumber(CreditCard.replaceFullWidthChars(this.target.value)); const oldValue = this.target.value; if (value !== oldValue) { this.target.selectionStart = this.target.selectionEnd = CreditCard.safeVal(value, this.target, (safeVal => { this.updateValue(safeVal); })); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CreditCardFormatDirective, deps: [{ token: i0.ElementRef }, { token: i1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.2", type: CreditCardFormatDirective, isStandalone: true, selector: "[ccNumber]", host: { listeners: { "keypress": "onKeypress($event)", "keydown": "onKeydown($event)", "keyup": "onKeyup()", "paste": "onPaste()", "change": "onChange()", "input": "onInput()" } }, exportAs: ["ccNumber"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CreditCardFormatDirective, decorators: [{ type: Directive, args: [{ selector: '[ccNumber]', exportAs: 'ccNumber', standalone: true, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.NgControl, decorators: [{ type: Self }, { type: Optional }] }], propDecorators: { onKeypress: [{ type: HostListener, args: ['keypress', ['$event']] }], onKeydown: [{ type: HostListener, args: ['keydown', ['$event']] }], onKeyup: [{ type: HostListener, args: ['keyup'] }], onPaste: [{ type: HostListener, args: ['paste'] }], onChange: [{ type: HostListener, args: ['change'] }], onInput: [{ type: HostListener, args: ['input'] }] } }); class CvcFormatDirective { constructor(el, control) { this.el = el; this.control = control; this.target = this.el.nativeElement; } /** * Updates the value to target element, or FormControl if exists. * @param value New input value. */ updateValue(value) { if (this.control) { this.control.control.setValue(value); } else { this.target.value = value; } } onKeypress(e) { if (!CreditCard.restrictNumeric(e) && !CreditCard.restrictCvc(e.which, this.target)) { e.preventDefault(); } } reformatCvc() { const val = CreditCard.replaceFullWidthChars(this.target.value) .replace(/\D/g, '') .slice(0, 4); const oldVal = this.target.value; if (val !== oldVal) { this.target.selectionStart = this.target.selectionEnd = CreditCard.safeVal(val, this.target, (safeVal => { this.updateValue(safeVal); })); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CvcFormatDirective, deps: [{ token: i0.ElementRef }, { token: i1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.2", type: CvcFormatDirective, isStandalone: true, selector: "[ccCVC]", host: { listeners: { "keypress": "onKeypress($event)", "paste": "reformatCvc()", "change": "reformatCvc()", "input": "reformatCvc()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CvcFormatDirective, decorators: [{ type: Directive, args: [{ selector: '[ccCVC]', standalone: true, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.NgControl, decorators: [{ type: Self }, { type: Optional }] }], propDecorators: { onKeypress: [{ type: HostListener, args: ['keypress', ['$event']] }], reformatCvc: [{ type: HostListener, args: ['paste'] }, { type: HostListener, args: ['change'] }, { type: HostListener, args: ['input'] }] } }); class ExpiryFormatDirective { constructor(el, control) { this.el = el; this.control = control; this.target = this.el.nativeElement; } /** * Updates the value to target element, or FormControl if exists. * @param value New input value. */ updateValue(value) { if (this.control) { this.control.control.setValue(value); } else { this.target.value = value; } } onKeypress(e) { if (CreditCard.restrictNumeric(e)) { if (CreditCard.restrictExpiry(e.which, this.target)) { this.formatExpiry(e); this.formatForwardSlashAndSpace(e); this.formatForwardExpiry(e); } } else { e.preventDefault(); return false; } } onKeydown(e) { if (CreditCard.restrictNumeric(e) && CreditCard.restrictExpiry(e.which, this.target)) { this.formatBackExpiry(e); } } onChange() { this.reformatExpiry(); } onInput() { this.reformatExpiry(); } formatExpiry(e) { const digit = String.fromCharCode(e.which); const val = `${this.target.value}${digit}`; if (!/^\d+$/.test(digit)) { return; } if (/^\d$/.test(val) && (val !== '0' && val !== '1')) { e.preventDefault(); this.updateValue(`0${val} / `); } else if (/^\d\d$/.test(val)) { e.preventDefault(); const m1 = parseInt(val[0], 10); const m2 = parseInt(val[1], 10); if (m2 > 2 && m1 !== 0) { this.updateValue(`0${m1} / ${m2}`); } else { this.updateValue(`${val} / `); } } } formatForwardSlashAndSpace(e) { const which = String.fromCharCode(e.which); const val = this.target.value; if (!(which === '/' || which === ' ')) { return false; } if (/^\d$/.test(val) && val !== '0') { this.updateValue(`0${val} / `); } } formatForwardExpiry(e) { const digit = String.fromCharCode(e.which); const val = this.target.value; if (!/^\d+$/.test(digit) && /^\d\d$/.test(val)) { this.updateValue(this.target.value = `${val} / `); } } formatBackExpiry(e) { const val = this.target.valueOf; if (e.which !== 8) { return; } if ((this.target.selectionStart != null) && this.target.selectionStart !== val.length) { return; } if (/\d\s\/\s$/.test(val)) { e.preventDefault(); this.updateValue(val.replace(/\d\s\/\s$/, '')); } } reformatExpiry() { const val = CreditCard.formatExpiry(CreditCard.replaceFullWidthChars(this.target.value)); const oldVal = this.target.value; if (val !== oldVal) { this.target.selectionStart = this.target.selectionEnd = CreditCard.safeVal(val, this.target, (safeVal => { this.updateValue(safeVal); })); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: ExpiryFormatDirective, deps: [{ token: i0.ElementRef }, { token: i1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.2", type: ExpiryFormatDirective, isStandalone: true, selector: "[ccExp]", host: { listeners: { "keypress": "onKeypress($event)", "keydown": "onKeydown($event)", "change": "onChange()", "input": "onInput()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: ExpiryFormatDirective, decorators: [{ type: Directive, args: [{ selector: '[ccExp]', standalone: true, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.NgControl, decorators: [{ type: Self }, { type: Optional }] }], propDecorators: { onKeypress: [{ type: HostListener, args: ['keypress', ['$event']] }], onKeydown: [{ type: HostListener, args: ['keydown', ['$event']] }], onChange: [{ type: HostListener, args: ['change'] }], onInput: [{ type: HostListener, args: ['input'] }] } }); const CREDIT_CARD_LIBRARY_DIRECTIVES = [ CreditCardFormatDirective, ExpiryFormatDirective, CvcFormatDirective, ]; class CreditCardDirectivesModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CreditCardDirectivesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.2", ngImport: i0, type: CreditCardDirectivesModule, imports: [CreditCardFormatDirective, ExpiryFormatDirective, CvcFormatDirective], exports: [CreditCardFormatDirective, ExpiryFormatDirective, CvcFormatDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CreditCardDirectivesModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.2", ngImport: i0, type: CreditCardDirectivesModule, decorators: [{ type: NgModule, args: [{ imports: [CREDIT_CARD_LIBRARY_DIRECTIVES], exports: [CREDIT_CARD_LIBRARY_DIRECTIVES], }] }] }); /* * Public API Surface of angular-cc-library */ /** * Generated bundle index. Do not edit. */ export { CreditCard, CreditCardDirectivesModule, CreditCardFormatDirective, CreditCardValidators, CvcFormatDirective, ExpiryFormatDirective }; //# sourceMappingURL=angular-cc-library.mjs.map