diff --git a/app/api/payments/[id]/edit/route.ts b/app/api/payments/[id]/edit/route.ts index b4926b2..4d96fb6 100644 --- a/app/api/payments/[id]/edit/route.ts +++ b/app/api/payments/[id]/edit/route.ts @@ -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 }, ); } diff --git a/app/api/payments/create/route.ts b/app/api/payments/create/route.ts index a361048..a4271c0 100644 --- a/app/api/payments/create/route.ts +++ b/app/api/payments/create/route.ts @@ -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 }, ); } diff --git a/app/remittance/RemittanceClient.tsx b/app/remittance/RemittanceClient.tsx index a8f770a..beb4ed7 100644 --- a/app/remittance/RemittanceClient.tsx +++ b/app/remittance/RemittanceClient.tsx @@ -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" /> diff --git a/app/unpaid/UnpaidClient.tsx b/app/unpaid/UnpaidClient.tsx index 6a7d591..5d5f3ee 100644 --- a/app/unpaid/UnpaidClient.tsx +++ b/app/unpaid/UnpaidClient.tsx @@ -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" /> diff --git a/docs/superpowers/plans/2026-09-18-jei-payments-and-remittance.md b/docs/superpowers/plans/2026-09-18-jei-payments-and-remittance.md index ac1704d..e4a6161 100644 --- a/docs/superpowers/plans/2026-09-18-jei-payments-and-remittance.md +++ b/docs/superpowers/plans/2026-09-18-jei-payments-and-remittance.md @@ -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" />