Skip to content

Commit 234f02d

Browse files
committed
refactor(useCardForm): always collect cardholderName (ACC-6925)
1 parent 15c3bb8 commit 234f02d

2 files changed

Lines changed: 30 additions & 32 deletions

File tree

src/Components/hooks/useCardForm.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,16 @@ function stripCardNumberForNative(formatted: string): string {
3131
return formatted.replace(/\s/g, '');
3232
}
3333

34+
// Display is MM/YY but Android's validator requires MM/YYYY; iOS accepts both.
35+
// Expand only once the year is fully typed (4-char "MM/YY"); partial edits
36+
// pass through unchanged so native can keep reporting "cannot be blank".
37+
function expandExpiryYearForNative(formatted: string): string {
38+
const match = /^(\d{2})\/(\d{2})$/.exec(formatted);
39+
return match ? `${match[1]}/20${match[2]}` : formatted;
40+
}
41+
3442
export function useCardForm(options: UseCardFormOptions = {}): UseCardFormReturn {
35-
const { collectCardholderName = false, onValidationChange, onMetadataChange, onBinDataChange } = options;
43+
const { onValidationChange, onMetadataChange, onBinDataChange } = options;
3644
const { isReady } = usePrimerCheckout();
3745

3846
// Field state
@@ -80,12 +88,12 @@ export function useCardForm(options: UseCardFormOptions = {}): UseCardFormReturn
8088
});
8189

8290
// Debounced sync to native
83-
const debouncedRef = useRef<
84-
DebouncedFunction<(data: { cardNumber: string; expiryDate: string; cvv: string; cardholderName?: string }) => void>
85-
>(null as any);
91+
const debouncedRef = useRef<DebouncedFunction<
92+
(data: { cardNumber: string; expiryDate: string; cvv: string; cardholderName: string }) => void
93+
> | null>(null);
8694
useEffect(() => {
8795
debouncedRef.current = debounce(
88-
(data: { cardNumber: string; expiryDate: string; cvv: string; cardholderName?: string }) => {
96+
(data: { cardNumber: string; expiryDate: string; cvv: string; cardholderName: string }) => {
8997
bridge.setRawData(data).catch(() => {});
9098
},
9199
DEBOUNCE_MS
@@ -98,16 +106,13 @@ export function useCardForm(options: UseCardFormOptions = {}): UseCardFormReturn
98106

99107
const syncToNative = useCallback(() => {
100108
const fields = fieldsRef.current;
101-
const data: { cardNumber: string; expiryDate: string; cvv: string; cardholderName?: string } = {
109+
debouncedRef.current?.({
102110
cardNumber: stripCardNumberForNative(fields.cardNumber),
103-
expiryDate: fields.expiryDate,
111+
expiryDate: expandExpiryYearForNative(fields.expiryDate),
104112
cvv: fields.cvv,
105-
};
106-
if (collectCardholderName) {
107-
data.cardholderName = fields.cardholderName;
108-
}
109-
debouncedRef.current?.(data);
110-
}, [collectCardholderName]);
113+
cardholderName: fields.cardholderName,
114+
});
115+
}, []);
111116

112117
// Updaters
113118
const updateCardNumber = useCallback(
@@ -197,7 +202,7 @@ export function useCardForm(options: UseCardFormOptions = {}): UseCardFormReturn
197202
setBinData(null);
198203
fieldsRef.current = { cardNumber: '', expiryDate: '', cvv: '', cardholderName: '' };
199204
debouncedRef.current?.cancel();
200-
bridge.setRawData({ cardNumber: '', expiryDate: '', cvv: '' }).catch(() => {});
205+
bridge.setRawData({ cardNumber: '', expiryDate: '', cvv: '', cardholderName: '' }).catch(() => {});
201206
}, [bridge.setRawData]);
202207

203208
return {

src/Components/types/CardFormTypes.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ export type CardFormField = 'cardNumber' | 'expiryDate' | 'cvv' | 'cardholderNam
66
export type CardFormErrors = Partial<Record<CardFormField, string>>;
77

88
export interface UseCardFormOptions {
9-
/** Whether to collect cardholder name. Default: false */
10-
collectCardholderName?: boolean;
119
/** Called when validation state changes */
1210
onValidationChange?: (isValid: boolean, errors: CardFormErrors) => void;
1311
/** Called when card metadata changes (e.g., network detection) */
@@ -17,42 +15,37 @@ export interface UseCardFormOptions {
1715
}
1816

1917
export interface UseCardFormReturn {
20-
/** Formatted card number (with spaces) */
18+
/** Formatted card number (with spaces). */
2119
cardNumber: string;
22-
/** Formatted expiry (MM/YY) */
20+
/** Formatted expiry as MM/YY. */
2321
expiryDate: string;
24-
/** CVV digits */
2522
cvv: string;
26-
/** Cardholder name */
2723
cardholderName: string;
2824

29-
/** Update card number (auto-formats with spaces) */
25+
/** Auto-formats with spaces. */
3026
updateCardNumber: (value: string) => void;
31-
/** Update expiry date (auto-formats MM/YY) */
27+
/** Auto-formats as MM/YY. */
3228
updateExpiryDate: (value: string) => void;
33-
/** Update CVV */
3429
updateCVV: (value: string) => void;
35-
/** Update cardholder name */
3630
updateCardholderName: (value: string) => void;
3731

38-
/** Overall form validity from native SDK */
32+
/** Overall form validity as reported by the native SDK. */
3933
isValid: boolean;
40-
/** Per-field errors (only for touched fields) */
34+
/** Per-field errors, surfaced only for touched fields. */
4135
errors: CardFormErrors;
42-
/** Mark a field as touched (show errors after blur) */
36+
/** Marks a field as touched so its error can appear on blur. */
4337
markFieldTouched: (field: CardFormField) => void;
4438

45-
/** Trigger tokenization. No-op if invalid or already submitting */
39+
/** Trigger tokenization. No-op if invalid or already submitting. */
4640
submit: () => Promise<void>;
47-
/** Whether submission is in progress */
4841
isSubmitting: boolean;
4942

50-
/** Required fields from merchant configuration */
43+
/** Fields the active client session requires. */
5144
requiredFields: PrimerInputElementType[];
5245

53-
/** Card network detection data */
46+
/** BIN / card network data from the native SDK. */
5447
binData: PrimerBinData | null;
5548

56-
/** Clear all fields and state */
49+
/** Clear all fields and reset local state. */
5750
reset: () => void;
5851
}

0 commit comments

Comments
 (0)