Add TPIN creation, TPIN-authorised transfers, MPIN reset and transfer history
- Create TPIN: current MPIN -> OTP -> encrypted TPIN via /tpin/create/*, from Services and from the transfer screen on TPIN_NOT_SET - IMPS initiate now sends encryptedTpin instead of encryptedMpin; handles INVALID_TPIN and TPIN_NOT_SET - Share one RSA-OAEP encryption and key-retry path for MPIN and TPIN - Forgot MPIN on sign-in and Reset MPIN in the Security Center - Fund transfer history list and detail, reachable from Home - Brand logo on sign-in and palette updates Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
abb14d5768
commit
f1def81eb7
@@ -0,0 +1,57 @@
|
||||
import { isRepeatedDigits, isSequentialDigits } from '../../../utils/validation';
|
||||
|
||||
/** A TPIN is always exactly four digits. */
|
||||
export const TPIN_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* 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 — so that one 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 (isSequentialDigits(value)) {
|
||||
return 'Pick a TPIN that is not a run of digits like 1234 or 4321';
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user