[PM-14883] Strip non-numeric characters in credit card number display… (#20070)

* [PM-14883] Strip non-numeric characters in credit card number display pipe

* [PM-14883] Move credit card number pipe to vault and add unit tests
This commit is contained in:
Shane Melton 2026-04-17 14:56:16 -07:00 committed by GitHub
parent a88dc4a0a0
commit 3140be4396
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 1 deletions

View File

@ -4,7 +4,6 @@ import { CommonModule } from "@angular/common";
import { Component, Input, OnChanges, SimpleChanges } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CreditCardNumberPipe } from "@bitwarden/angular/pipes/credit-card-number.pipe";
import { EventCollectionService, EventType } from "@bitwarden/common/dirt/event-logs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -15,6 +14,7 @@ import {
IconButtonModule,
} from "@bitwarden/components";
import { CreditCardNumberPipe } from "../../pipes/credit-card-number.pipe";
import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush

View File

@ -13,6 +13,7 @@ export { OrgIconDirective } from "./components/org-icon.directive";
export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive";
export { DarkImageSourceDirective } from "./components/dark-image-source.directive";
export { GetOrgNameFromIdPipe } from "./pipes/get-organization-name.pipe";
export { CreditCardNumberPipe } from "./pipes/credit-card-number.pipe";
export * from "./cipher-view";
export * from "./cipher-form";

View File

@ -0,0 +1,49 @@
import { CreditCardNumberPipe } from "./credit-card-number.pipe";
describe("CreditCardNumberPipe", () => {
let pipe: CreditCardNumberPipe;
beforeEach(() => {
pipe = new CreditCardNumberPipe();
});
it("formats a 16-digit card as 4-4-4-4", () => {
expect(pipe.transform("4111111111111111", "Visa")).toBe("4111 1111 1111 1111");
});
it("formats a 15-digit Amex as 4-6-5", () => {
expect(pipe.transform("378282246310005", "Amex")).toBe("3782 822463 10005");
});
it("formats a 14-digit Diners Club as 4-6-4", () => {
expect(pipe.transform("36259600000004", "Diners Club")).toBe("3625 960000 0004");
});
it("formats a 19-digit UnionPay as 6-13", () => {
expect(pipe.transform("6200000000000000003", "UnionPay")).toBe("620000 0000000000003");
});
it("falls back to Other format for unknown brands", () => {
expect(pipe.transform("4111111111111111", "UnknownBrand")).toBe("4111 1111 1111 1111");
});
it("strips non-numeric characters before formatting", () => {
expect(pipe.transform("4111-1111-1111-1111", "Visa")).toBe("4111 1111 1111 1111");
});
it("strips spaces before formatting", () => {
expect(pipe.transform("4111 1111 1111 1111", "Visa")).toBe("4111 1111 1111 1111");
});
it("strips mixed non-numeric characters", () => {
expect(pipe.transform("4111.1111/1111#1111", "Visa")).toBe("4111 1111 1111 1111");
});
it("appends remaining digits when number exceeds expected length", () => {
expect(pipe.transform("41111111111111115555", "Visa")).toBe("4111 1111 1111 1111 5555");
});
it("handles a card number that is only non-numeric characters", () => {
expect(pipe.transform("----", "Visa")).toBe(" ");
});
});

View File

@ -33,6 +33,8 @@ const numberFormats: Record<string, CardRuleEntry[]> = {
})
export class CreditCardNumberPipe implements PipeTransform {
transform(creditCardNumber: string, brand: string): string {
creditCardNumber = creditCardNumber.replace(/\D/g, "");
let rules = numberFormats[brand];
if (rules == null) {