import { isRepeatedDigits, isSequentialDigits } from '../../../utils/validation'; /** A TPIN is always exactly four digits. */ export const TPIN_LENGTH = 4; /** The shortest run up or down the backend's TPIN policy refuses. */ const SEQUENCE_RUN_LENGTH = 3; /** * True when any three neighbouring digits run straight up or down — 1235 (123), * 5987 (987) and 1234 alike. The TPIN policy refuses "a sequence of three * digits" and answers "TPIN cannot contain sequential digits", so a run * anywhere counts, not only one spanning all four. No wrap-around: 8901 is not * a run. */ export const containsSequentialRun = (value: string) => { for (let start = 0; start + SEQUENCE_RUN_LENGTH <= value.length; start += 1) { if (isSequentialDigits(value.slice(start, start + SEQUENCE_RUN_LENGTH))) { return true; } } return false; }; /** * Any four digits — for ENTERING the TPIN on a transfer. Deliberately loose: * whether they are the right four is the server's to say, with INVALID_TPIN. */ export const isValidTpinEntry = (value: string) => new RegExp(`^[0-9]{${TPIN_LENGTH}}$`).test(value); /** * Inline copy for why a NEW TPIN was refused before it ever left the device — * for choosing one, not for entering one on a transfer. * * `mobileNumber`, when known, adds the "not the last four digits of your * registered mobile" rule, so the customer is told before a round trip rather * than after one. * * The backend stays the final authority. It also refuses a TPIN matching the * customer's date of birth, which the app never holds, and — on a reset — the * current TPIN or any of the previous three, which only the server's history * knows. Those can only arrive as a VALIDATION_ERROR on confirm. */ export const describeTpinProblem = (value: string, mobileNumber?: string): string | undefined => { if (value.length === 0) { return undefined; } if (!/^[0-9]*$/.test(value)) { return 'Digits only'; } if (value.length !== TPIN_LENGTH) { return `A TPIN is ${TPIN_LENGTH} digits`; } if (isRepeatedDigits(value)) { return 'Pick a TPIN that is not the same digit repeated'; } if (containsSequentialRun(value)) { return 'Pick a TPIN without a run of digits like 123 or 987'; } const mobileDigits = mobileNumber?.replace(/\D/g, '') ?? ''; if (mobileDigits.length >= TPIN_LENGTH && mobileDigits.slice(-TPIN_LENGTH) === value) { return 'Pick a TPIN that is not the last four digits of your mobile number'; } return undefined; }; /** The stricter check for CHOOSING a TPIN. */ export const isAcceptableNewTpin = (value: string, mobileNumber?: string) => isValidTpinEntry(value) && describeTpinProblem(value, mobileNumber) === undefined;