EarnMorev1.0.0
masking.tstypescript
/**
 * earnmore — PAN masking and SAD redaction utilities (PCI-DSS Req 3.3.1 / 3.5)
 *
 * PCI-DSS Req 3.5:
 *   Primary Account Numbers (PANs) must be masked when displayed so that only
 *   personnel with a legitimate business need can see more than the first six
 *   or last four digits.
 *
 *   Compliant mask format: first 6 digits (BIN) + masked middle + last 4 digits.
 *   Example: "0011382910471836" → "001138xxxxxx1836"
 *
 * PCI-DSS Req 3.3.1:
 *   Sensitive Authentication Data (SAD) — including CVV/CVC — must not be
 *   stored after authorization is complete, even if encrypted.
 *   Use redactCardResult() to strip the CVV before persisting a card record.
 */

import { CardResult, StorableCardData } from "./types";

/**
 * Masks a 16-digit PAN per PCI-DSS Req 3.5.
 *
 * Format: first 6 digits (BIN/IIN) + 6 masked chars + last 4 digits.
 * The 6 masked characters in the middle are shown as lowercase 'x'.
 *
 * Example: "0011382910471836" → "001138xxxxxx1836"
 *
 * @param pan  16-digit PAN string (digits only)
 * @returns    Masked PAN string
 * @throws {Error} If PAN is not exactly 16 digits
 */
export function maskPAN(pan: string): string {
  if (!/^\d{16}$/.test(pan)) {
    throw new Error(`Invalid PAN: must be exactly 16 digits.`);
  }
  // first 6 (BIN) + 6 masked + last 4
  return pan.substring(0, 6) + "xxxxxx" + pan.substring(12);
}

/**
 * Strips Sensitive Authentication Data (SAD) from a CardResult, returning
 * only Cardholder Data (CHD) that is safe to store per PCI-DSS Req 3.3.1.
 *
 * The CVV is intentionally excluded. The PAN is automatically masked.
 * If you need the full PAN for storage, use encryptPAN() from storage.ts.
 *
 * @param result  Full CardResult from generate()
 * @returns       StorableCardData with maskedPan and expiry only (no CVV)
 */
export function redactCardResult(result: CardResult): StorableCardData {
  return {
    maskedPan: maskPAN(result.pan),
    expiry: result.expiry,
    keyId: result.keyId,
  };
}