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:
parent
472ae5c0f0
commit
346a60cf97
5 changed files with 14 additions and 9 deletions
|
|
@ -3,7 +3,9 @@ import { revalidatePath } from "next/cache";
|
||||||
import { getPb } from "../../../../lib/pb";
|
import { getPb } from "../../../../lib/pb";
|
||||||
|
|
||||||
const BATCHES = "JEI_payment_batches";
|
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}$/;
|
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
@ -37,7 +39,7 @@ export async function POST(
|
||||||
// Validate
|
// Validate
|
||||||
if (!BATCH_REF_PATTERN.test(batchRef)) {
|
if (!BATCH_REF_PATTERN.test(batchRef)) {
|
||||||
return NextResponse.json(
|
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 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,11 @@ export async function POST(req: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. batch_ref
|
// 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(
|
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 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ export default function RemittanceClient({ batches }: { batches: BatchWithInvoic
|
||||||
type="text"
|
type="text"
|
||||||
value={editForm.batch_ref}
|
value={editForm.batch_ref}
|
||||||
onChange={(e) => setEditForm({ ...editForm, batch_ref: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, batch_ref: e.target.value })}
|
||||||
pattern="[A-Za-z0-9_-]{1,50}"
|
pattern="[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}"
|
||||||
required
|
required
|
||||||
className="w-full border rounded px-3 py-2 font-mono"
|
className="w-full border rounded px-3 py-2 font-mono"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ export default function UnpaidClient({ initialRecords }: { initialRecords: Invoi
|
||||||
type="text"
|
type="text"
|
||||||
value={batchRef}
|
value={batchRef}
|
||||||
onChange={(e) => setBatchRef(e.target.value)}
|
onChange={(e) => setBatchRef(e.target.value)}
|
||||||
pattern="[A-Za-z0-9_-]{1,50}"
|
pattern="[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}"
|
||||||
required
|
required
|
||||||
className="w-full border rounded px-3 py-2 font-mono"
|
className="w-full border rounded px-3 py-2 font-mono"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -378,9 +378,10 @@ export async function POST(req: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. batch_ref
|
// 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(
|
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 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -984,7 +985,7 @@ export default function UnpaidClient({ initialRecords }: { initialRecords: Invoi
|
||||||
type="text"
|
type="text"
|
||||||
value={batchRef}
|
value={batchRef}
|
||||||
onChange={(e) => setBatchRef(e.target.value)}
|
onChange={(e) => setBatchRef(e.target.value)}
|
||||||
pattern="[A-Za-z0-9_-]{1,50}"
|
pattern="[A-Za-z0-9 _\-\/.,:()&#'+]{1,100}"
|
||||||
required
|
required
|
||||||
className="w-full border rounded px-3 py-2 font-mono"
|
className="w-full border rounded px-3 py-2 font-mono"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue