Problem
The "Submit Batch" button re-enables as soon as the initial queueing
request returns — while the payment job is still running in the background
— letting a user submit an overlapping second job for the same batch.
Severity: High
Location: components/dashboard/BatchReview.tsx:151-166,440-447, contexts/BatchFlowContext.tsx:345-374
Description
BatchReview's handleSubmit sets a local isSubmitting flag, awaits
onSubmit(...), then clears it in finally. But the context-level
onSubmit resolves as soon as /api/batch-submit returns its initial
queueing response — it calls startPolling(jobId, publicKey) without
awaiting it, so the actual job status (queued/processing/complete) is never
reflected back to the caller. The submit button's disabled prop
(components/dashboard/BatchReview.tsx:445) reads only this local
isSubmitting flag, not the context's job-status state, so it re-enables
almost immediately. Because the idempotency key is derived from the payment
list itself, changing the skip/convert selection before resubmitting
produces a genuinely different key — the backend treats it as a new job, not
a duplicate.
Steps to Reproduce
- Submit a batch containing recipients A and B; receive the queued
response.
- Before the job reaches a terminal state, change the review selection to
skip B.
- Submit again — the button is enabled because the local flag was already
cleared.
- The new job (containing only A) runs alongside the original job (A and
B) — recipient A can be paid by both.
Expected Behavior
Submission controls should stay locked based on the actual job lifecycle
state, not a component-local flag that clears on the initial HTTP response.
Suggested Fix Approach
Drive the button's disabled state from the context's job-status (queued /
processing / terminal), and block a second submission while an active job
exists for the same review session.
Acceptance Criteria
Problem
The "Submit Batch" button re-enables as soon as the initial queueing
request returns — while the payment job is still running in the background
— letting a user submit an overlapping second job for the same batch.
Severity: High
Location:
components/dashboard/BatchReview.tsx:151-166,440-447,contexts/BatchFlowContext.tsx:345-374Description
BatchReview'shandleSubmitsets a localisSubmittingflag, awaitsonSubmit(...), then clears it infinally. But the context-levelonSubmitresolves as soon as/api/batch-submitreturns its initialqueueing response — it calls
startPolling(jobId, publicKey)withoutawaiting it, so the actual job status (queued/processing/complete) is never
reflected back to the caller. The submit button's
disabledprop(
components/dashboard/BatchReview.tsx:445) reads only this localisSubmittingflag, not the context's job-status state, so it re-enablesalmost immediately. Because the idempotency key is derived from the payment
list itself, changing the skip/convert selection before resubmitting
produces a genuinely different key — the backend treats it as a new job, not
a duplicate.
Steps to Reproduce
response.
skip B.
cleared.
B) — recipient A can be paid by both.
Expected Behavior
Submission controls should stay locked based on the actual job lifecycle
state, not a component-local flag that clears on the initial HTTP response.
Suggested Fix Approach
Drive the button's disabled state from the context's job-status (queued /
processing / terminal), and block a second submission while an active job
exists for the same review session.
Acceptance Criteria
state
overlapping submission
prior job is still active