diff --git a/app/api/v1/recipients/route.ts b/app/api/v1/recipients/route.ts index 3e11eb19..50422005 100644 --- a/app/api/v1/recipients/route.ts +++ b/app/api/v1/recipients/route.ts @@ -152,6 +152,31 @@ export const POST = withRateLimit(async (request: NextRequest) => { ); } + const trimmedInstitutionCode = String(institutionCode).trim(); + + // NUBAN is 10 digits; SAFAKEPC uses 6 digits - reject invalid length so we don't save 11-digit (e.g. phone) as beneficiary + const digits = String(accountIdentifier).replace(/\D/g, ""); + const requiredLen = trimmedInstitutionCode === "SAFAKEPC" ? 6 : 10; + if (digits.length !== requiredLen) { + trackApiError( + request, + "/api/v1/recipients", + "POST", + new Error("Invalid account identifier length"), + 400, + ); + return NextResponse.json( + { + success: false, + error: + requiredLen === 10 + ? "Please enter a valid 10-digit account number." + : "Please enter a valid 6-digit account number.", + }, + { status: 400 }, + ); + } + // Validate type if (!["bank", "mobile_money"].includes(type)) { trackApiError( @@ -199,7 +224,7 @@ export const POST = withRateLimit(async (request: NextRequest) => { } } - // Insert recipient (upsert on unique constraint) + // Insert recipient (upsert on unique constraint) - store sanitized digits so DB has consistent format const { data, error } = await supabaseAdmin .from("saved_recipients") .upsert( @@ -208,8 +233,8 @@ export const POST = withRateLimit(async (request: NextRequest) => { normalized_wallet_address: walletAddress, name: name.trim(), institution: institution.trim(), - institution_code: institutionCode.trim(), - account_identifier: accountIdentifier.trim(), + institution_code: trimmedInstitutionCode, + account_identifier: digits, type, }, { @@ -244,14 +269,14 @@ export const POST = withRateLimit(async (request: NextRequest) => { const responseTime = Date.now() - startTime; trackApiResponse("/api/v1/recipients", "POST", 200, responseTime, { wallet_address: walletAddress, - institution_code: institutionCode, + institution_code: trimmedInstitutionCode, type, }); // Track business event trackBusinessEvent("Recipient Saved", { wallet_address: walletAddress, - institution_code: institutionCode, + institution_code: trimmedInstitutionCode, type, }); diff --git a/app/components/recipient/RecipientDetailsForm.tsx b/app/components/recipient/RecipientDetailsForm.tsx index 3ac117d7..f02f411f 100644 --- a/app/components/recipient/RecipientDetailsForm.tsx +++ b/app/components/recipient/RecipientDetailsForm.tsx @@ -243,20 +243,29 @@ export const RecipientDetailsForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedInstitution, isManualEntry]); - // Fetch recipient name based on institution and account identifier + // Fetch recipient name based on institution and account identifier (only when exactly 10-digit NUBAN or 6-digit SAFAKEPC) useEffect(() => { let timeoutId: NodeJS.Timeout; const getRecipientName = async () => { if (!isManualEntry) return; - if ( - !institution || - !accountIdentifier || - accountIdentifier.toString().length < - (selectedInstitution?.code === "SAFAKEPC" ? 6 : 10) - ) + const digits = String(accountIdentifier ?? "").replace(/\D/g, ""); + const requiredLen = selectedInstitution?.code === "SAFAKEPC" ? 6 : 10; + + if (!institution || !accountIdentifier || digits.length !== requiredLen) { + if (accountIdentifier && digits.length > 0 && digits.length !== requiredLen) { + setRecipientNameError( + requiredLen === 10 + ? "Please enter a valid 10-digit account Number." + : "Invalid account number. Please enter a 6-digit account number.", + ); + } else { + setRecipientNameError(""); + } return; + } + setRecipientNameError(""); setIsFetchingRecipientName(true); setValue("recipientName", ""); @@ -380,19 +389,27 @@ export const RecipientDetailsForm = ({ - {/* Account number */} + {/* Account number - NUBAN is 10 digits; SAFAKEPC uses 6 digits */}
{ + const digits = String(value ?? "").replace(/\D/g, ""); + const requiredLen = selectedInstitution?.code === "SAFAKEPC" ? 6 : 10; + if (digits.length !== requiredLen) { + return requiredLen === 10 + ? "Please enter a valid 10-digit account Number." + : "Invalid account number. Please enter a 6-digit account number."; + } + return true; }, onChange: () => setIsManualEntry(true), })}