feat(payments): relax batch_ref regex to allow spaces, slashes, dots, colons, etc.

The previous rule ^[A-Za-z0-9_-]{1,50}$ rejected common bank/POP
references that include punctuation [e.g. 'Bank ref 26/09/18',
'INV/2026.09:18 #3', 'POP 12345']. New rule permits letters, digits,
space, underscore, hyphen, slash, dot, comma, colon, parens,
ampersand, hash, apostrophe, plus; length 1-100. Still rejects
characters that break filesystems/URLs [e.g. < > " ? * | \ ;].

Files updated:
- app/api/payments/create/route.ts   [inline regex + new error msg]
- app/api/payments/[id]/edit/route.ts [BATCH_REF_PATTERN + new error msg]
- app/unpaid/UnpaidClient.tsx        [HTML pattern attribute]
- app/remittance/RemittanceClient.tsx [HTML pattern attribute]
- docs/superpowers/plans/2026-09-18-jei-payments-and-remittance.md [plan doc]
This commit is contained in:
Mavis 2026-09-19 01:13:15 +02:00
parent 472ae5c0f0
commit 346a60cf97
5 changed files with 14 additions and 9 deletions

View file

@ -3,7 +3,9 @@ import { revalidatePath } from "next/cache";
import { getPb } from "../../../../lib/pb";
const BATCHES = "JEI_payment_batches";
const BATCH_REF_PATTERN = /^[A-Za-z0-9_-]{1,50}$/;
// Permissive: letters/digits/space/_-/,.,:()&#'+, length 1-100.
// Rejects only characters that break filesystems/URLs (< > " ? * | \ ; etc).
const BATCH_REF_PATTERN = /^[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}$/;
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
export const dynamic = "force-dynamic";
@ -37,7 +39,7 @@ export async function POST(
// Validate
if (!BATCH_REF_PATTERN.test(batchRef)) {
return NextResponse.json(
{ error: "batch_ref must match ^[A-Za-z0-9_-]{1,50}$" },
{ error: "batch_ref must be 1-100 chars: letters, digits, space, _ - / . , : ( ) & # ' +" },
{ status: 400 },
);
}

View file

@ -40,9 +40,11 @@ export async function POST(req: NextRequest) {
}
// 2. batch_ref
if (!/^[A-Za-z0-9_-]{1,50}$/.test(batchRef)) {
// Permissive: letters/digits/space/_-/,.,:()&#'+, length 1-100.
// Rejects only characters that break filesystems/URLs (< > " ? * | \ ; etc).
if (!/^[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}$/.test(batchRef)) {
return NextResponse.json(
{ error: "batch_ref must match ^[A-Za-z0-9_-]{1,50}$" },
{ error: "batch_ref must be 1-100 chars: letters, digits, space, _ - / . , : ( ) & # ' +" },
{ status: 400 },
);
}

View file

@ -158,7 +158,7 @@ export default function RemittanceClient({ batches }: { batches: BatchWithInvoic
type="text"
value={editForm.batch_ref}
onChange={(e) => setEditForm({ ...editForm, batch_ref: e.target.value })}
pattern="[A-Za-z0-9_-]{1,50}"
pattern="[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}"
required
className="w-full border rounded px-3 py-2 font-mono"
/>

View file

@ -222,7 +222,7 @@ export default function UnpaidClient({ initialRecords }: { initialRecords: Invoi
type="text"
value={batchRef}
onChange={(e) => setBatchRef(e.target.value)}
pattern="[A-Za-z0-9_-]{1,50}"
pattern="[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}"
required
className="w-full border rounded px-3 py-2 font-mono"
/>

View file

@ -378,9 +378,10 @@ export async function POST(req: NextRequest) {
}
// 2. batch_ref
if (!/^[A-Za-z0-9_-]{1,50}$/.test(batchRef)) {
// Permissive: letters/digits/space/_-/,.,:()&#'+, length 1-100.
if (!/^[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}$/.test(batchRef)) {
return NextResponse.json(
{ error: "batch_ref must match ^[A-Za-z0-9_-]{1,50}$" },
{ error: "batch_ref must be 1-100 chars: letters, digits, space, _ - / . , : ( ) & # ' +" },
{ status: 400 },
);
}
@ -984,7 +985,7 @@ export default function UnpaidClient({ initialRecords }: { initialRecords: Invoi
type="text"
value={batchRef}
onChange={(e) => setBatchRef(e.target.value)}
pattern="[A-Za-z0-9_-]{1,50}"
pattern="[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}"
required
className="w-full border rounded px-3 py-2 font-mono"
/>