diff --git a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts index efe668005f..62c111a218 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/validators.spec.ts @@ -1,7 +1,46 @@ import { FormControl } from '@angular/forms'; +import { validateCreditCard } from '../validators/credit-card.validator'; import { validateRequired } from '../validators/required.validator'; describe('Validators', () => { + describe('Credit Card Validator', () => { + const error = { creditCardNumber: true }; + + test.each` + input | expected + ${'0'} | ${error} + ${'5105105105105100' /* Mastercard */} | ${null} + ${'5105105105105101' /* Mastercard */} | ${error} + ${'5105 1051 0510 5100'} | ${null} + ${'5105-1051-0510-5100'} | ${null} + ${'5105 - 1051 - 0510 - 5100'} | ${null} + ${'4111111111111111' /*Visa*/} | ${null} + ${'4111111111111112' /*Visa*/} | ${error} + ${'4012888888881881' /* Visa */} | ${null} + ${'4012888888881882' /* Visa */} | ${error} + ${'4222222222222' /* Visa */} | ${null} + ${'4222222222223' /* Visa */} | ${error} + ${'378282246310005' /* American Express */} | ${null} + ${'378282246310006' /* American Express */} | ${error} + ${'6011111111111117' /* Discover */} | ${null} + ${'6011111111111118' /* Discover */} | ${error} + `('should return $expected when input is $input', ({ input, expected }) => { + const control = new FormControl(input, [validateCreditCard()]); + control.markAsDirty({ onlySelf: true }); + control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + + expect(control.errors).toEqual(expected); + }); + + it('should return null when control is pristine', () => { + const invalidNumber = '5105105105105101'; + const control = new FormControl(invalidNumber, [validateCreditCard()]); + // control is not dirty + + expect(control.valid).toBe(true); + }); + }); + describe('Required Validator', () => { const error = { required: true }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts new file mode 100644 index 0000000000..0436068d02 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/credit-card.validator.ts @@ -0,0 +1,32 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; + +export interface CreditCardError { + creditCardNumber: true; +} + +export function validateCreditCard(): ValidatorFn { + return (control: AbstractControl): CreditCardError | null => { + if (control.pristine) return null; + + return isValidCreditCard(String(control.value)) ? null : { creditCardNumber: true }; + }; +} + +function isValidCreditCard(value: string): boolean { + value = value.replace(/[ -]/g, ''); + + if (!/^[0-9]{13,19}$/.test(value)) return false; + + let checksum = 0; + let multiplier = 1; + + for (let i = value.length; i > 0; i--) { + const digit = Number(value[i - 1]) * multiplier; + /* tslint:disable-next-line:no-bitwise */ + checksum += (digit % 10) + ~~(digit / 10); + + multiplier = (multiplier * 2) % 3; + } + + return checksum % 10 === 0; +} diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index 0d0a6fb2e9..d353ed0515 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -1,8 +1,11 @@ import { Validators } from '@angular/forms'; +import { validateCreditCard } from './credit-card.validator'; import { validateRequired } from './required.validator'; +export * from './credit-card.validator'; export * from './required.validator'; export const AbpValidators = { + creditCard: validateCreditCard, email: () => Validators.email, required: validateRequired, };