Skip to content

Conversation

Copy link

Copilot AI commented Dec 17, 2025

Batch payment CSV validation previously stopped at the first invalid username, requiring multiple upload iterations to identify all errors. For users processing 130+ recipients, finding 1-2 invalid entries meant repeated uploads.

Changes

Validation logic (apps/dashboard/app/batch-payments/utils.ts)

  • processRecords() now collects all validation errors before returning
  • Continues processing remaining records when username lookup fails
  • Returns aggregated error message with all invalid usernames
// Before: stopped at first error
if (getDefaultWalletID.error) {
  return new Error(getDefaultWalletID.message)
}

// After: collects all errors
if (getDefaultWalletID.error) {
  validationErrors.push(`${record.username}: ${getDefaultWalletID.message}`)
  continue
}

Error display (apps/dashboard/components/batch-payments/index.tsx)

  • Added whiteSpace: "pre-line" to modal for multi-line error rendering

Tests (apps/dashboard/test/unit/batch-payments-utils.test.ts)

  • Validates error collection across multiple invalid usernames
  • Ensures valid records still process correctly

Example output

Invalid username(s) found:

bob_satoshi_TYPO: No wallet found for this user
frank_lightning_WRONG: User not found
zoe_btc_ERROR: No wallet found for this user

Users can now fix all invalid usernames in one iteration instead of discovering them sequentially.

Original prompt

This section details on the original issue you should resolve

<issue_title>Feature Suggestion – Batch Payment Validation for Blink</issue_title>
<issue_description>Migrated from GaloyMoney/blink#4804
Originally created by @Divoar on 4/17/2025, 7:16:45 AM

Hello Blink Team,

It would be a huge improvement to have a validation feature within the batch payment option, where the system can identify and return errors for invalid usernames upon uploading the .csv file. This would make it easier to detect and fix incorrect addresses before proceeding with the payment.

I’m currently trying to disburse sats to over 130 Blink addresses for The Core Cohort 6 graduates, and it’s quite difficult to identify the one or two incorrect entries that block the entire batch. Additionally, some students delay in confirming their usernames, which slows down the whole process.

Having this feature would make disbursements much smoother and faster, especially for educators or project leads managing bulk payments.

Thanks so much for all the great work you're doing at Blink — it’s really powering grassroots Bitcoin adoption.

Best
Felix
</issue_description>

<agent_instructions>Check #69 (comment) for specific implementation hints.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@k9ert Just had a discussion with claude about it. Here is the result:

Deep Dive: Batch Payment Validation

Relevant Files

File Purpose
`apps/dashboard/components/batch-payments/index.tsx` Main component, orchestrates file upload and payment processing
`apps/dashboard/app/batch-payments/utils.ts` Primary suspect - `validateCSV()` and `processRecords()` functions
`apps/dashboard/app/batch-payments/index.types.ts` Type definitions (CSVRecord, ProcessedRecords)
`apps/dashboard/app/batch-payments/get-user-details-action.ts` Server action - looks up walletId by username
`services/graphql/queries/get-user-wallet-id.ts` GraphQL query `accountDefaultWallet(username)`

Current Flow (utils.ts)

  1. `validateCSV()` (L24-140): Parses CSV, validates headers/fields, aggregates totals

    • Returns generic errors: 'Invalid CSV Format', 'Missing required headers', 'Record with missing required field found'
    • Does NOT validate usernames at this stage
  2. `processRecords()` (L142-177): Validates usernames one-by-one via GraphQL

    • HERE IS THE BUG: On first invalid username, returns error and stops
    • Error: `No wallet found for the user ${record.username}`
    • Does NOT continue to check remaining usernames

Root Cause Hypothesis

The feature request maps to this code path:
```
processRecords() → getUserDetailsAction() → getWalletDetailsByUsername()
```

When a username is invalid, the function returns immediately (L156-157):
```typescript
if (getDefaultWalletID.error) {
return new Error(getDefaultWalletID.message)
}
```

This stops processing at the first bad username instead of collecting all errors.

Issues From @pretyflaco's Comment

  1. Underscore usernames fail - Likely GraphQL/backend Username regex validation issue
  2. >100 usernames error - No explicit limit in frontend code; may be backend rate limiting
  3. Generic errors - Confirmed in code: errors don't identify which username failed

Related Issues

Implementation Approach (for future dev)

To fix, `processRecords()` should:

  1. Collect all username validation errors in an array
  2. Continue processing all records
  3. Return detailed error with list of invalid usernames
  4. UI should display which specific usernames failed validation"</comment_new>
    <comment_new>@grimen
    @pretyflaco You can also use "Assign to Copilot" and work that way through GitHub integrated tools as a workflow. </comment_new>
    <comment_new>@grimen

Additional issues reported with batch payments:

  • usernames that contain "_" underscore cause an error despite being valid usernames
  • uploading CSV with more than 100 usernames gives error
  • uploading a sheet with erroneous username gives too generic error without specifying culprit which makes it very hard to find the misspelled / invalid usernames

Regarding these issues they seem to not be directly tied to this feature request, so it would make sense to break this out to BUG ticket(s) to not mash it all und...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits December 17, 2025 15:31
- Modified processRecords() to continue validation even when encountering invalid usernames
- Collect all validation errors and return them together
- Added comprehensive unit tests for the new validation behavior

Co-authored-by: k9ert <117085+k9ert@users.noreply.github.com>
- Added whiteSpace: pre-line CSS to preserve line breaks in error messages
- Improved error message formatting with extra spacing

Co-authored-by: k9ert <117085+k9ert@users.noreply.github.com>
Copilot AI changed the title [WIP] Add batch payment validation for invalid usernames Collect all invalid usernames in batch payment validation Dec 17, 2025
Copilot AI requested a review from k9ert December 17, 2025 15:38
@pretyflaco
Copy link

pretyflaco commented Dec 17, 2025

Asked Claude Opus 4.5 to review:

PR Review: #372 - Collect all invalid usernames in batch payment validation

Summary

PR 372 effectively addresses Issue #69. The issue requested a validation feature that identifies and returns ALL errors for invalid usernames when uploading a CSV file, rather than failing on the first invalid entry.


Changes Made

1. apps/dashboard/app/batch-payments/utils.ts

The processRecords function was modified to collect validation errors instead of returning immediately:

  • const validationErrors: string[] = []

    const getDefaultWalletID = await getUserDetailsAction({ username: record.username })
    if (getDefaultWalletID.error) {

  • return new Error(getDefaultWalletID.message)
  • validationErrors.push(${record.username}: ${getDefaultWalletID.message})
  • continue
    }

if (!getDefaultWalletID.responsePayload) {

  • return new Error(No wallet found for the user ${record.username})
  • validationErrors.push(${record.username}: No wallet found for this user)

  • continue
    }

  • if (validationErrors.length > 0) {

  • const errorMessage = Invalid username(s) found:\n\n${validationErrors.join("\n")}

  • return new Error(errorMessage)

  • }2. apps/dashboard/components/batch-payments/index.tsx

Added CSS to properly display multi-line error messages:

  • {modalDetails.message}

  • data-testid="batch-payments-modal-message"
  • style={{ whiteSpace: "pre-line" }}
  • {modalDetails.message}
  • **3. New test file: `apps/dashboard/test/unit/batch-payments-utils.test.ts`**

Added comprehensive unit tests (216 lines) covering:

  • ✅ Collecting multiple invalid usernames in a single error
  • ✅ Processing all valid usernames successfully
  • ✅ Skipping records with zero/negative amounts
  • ✅ Handling users without wallets

✅ Does it Fix Issue #69?

YES - The PR properly addresses the core problem:

Issue Requirement PR Implementation
Identify invalid usernames on CSV upload ✅ Validates all usernames before failing
Return errors for ALL invalid entries ✅ Collects all errors in validationErrors[]
Make it easier to detect incorrect addresses ✅ Shows all invalid usernames with specific error messages
Don't block entire batch on first error ✅ Uses continue to keep processing

🔶 Suggestions for Improvement

  1. Add CSV row numbers - For 130+ entries, users would benefit from knowing which row has the error:
    validationErrors.push(Row ${index + 2}: ${record.username}: ${getDefaultWalletID.message})
    2. Summary count - Could enhance the message:
    const errorMessage = ${validationErrors.length} invalid username(s) found:\n\n${validationErrors.join("\n")}
    3. Performance consideration - For large batches (130+ records), sequential API calls will be slow. Consider parallelizing with Promise.allSettled() in a future enhancement.

  2. Missing edge case test - No test for scenario with both zero-amount records AND invalid usernames mixed together.


Verdict: APPROVE

The PR correctly implements the feature requested in Issue #69. Felix (the issue author) wanted to disburse sats to 130+ Blink addresses without having to discover invalid usernames one at a time. This PR enables exactly that - all invalid usernames are collected and displayed together, allowing users to fix all issues in a single pass before re-uploading.

The code is clean, follows existing patterns, includes good test coverage, and properly handles the UI display of multi-line error messages.

@grimen grimen requested a review from Copilot December 22, 2025 09:59
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances batch payment CSV validation by collecting all invalid usernames in a single pass, eliminating the need for multiple upload iterations. Previously, validation stopped at the first error, forcing users processing 130+ recipients to re-upload repeatedly to discover each invalid username.

Key changes:

  • Modified processRecords() to accumulate validation errors instead of failing fast
  • Enhanced error display to support multi-line formatted error messages
  • Added comprehensive test coverage for batch validation scenarios

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
apps/dashboard/app/batch-payments/utils.ts Refactored validation logic to collect all errors before returning, enabling single-pass validation
apps/dashboard/components/batch-payments/index.tsx Added CSS styling to support multi-line error message display
apps/dashboard/test/unit/batch-payments-utils.test.ts Added test suite validating error aggregation and successful processing of mixed valid/invalid records

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 180 to 183
if (validationErrors.length > 0) {
const errorMessage = `Invalid username(s) found:\n\n${validationErrors.join("\n")}`
return new Error(errorMessage)
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded error message prefix 'Invalid username(s) found:' could be extracted as a constant for easier maintenance and potential internationalization. Consider defining it at the module level alongside other constants.

Copilot uses AI. Check for mistakes.
jest.clearAllMocks()
})

it("should collect all invalid usernames and return them in a single error", async () => {
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test description uses 'single error' which could be ambiguous. Consider rephrasing to 'should collect all invalid usernames and return aggregated error message' to better describe the batched error behavior.

Suggested change
it("should collect all invalid usernames and return them in a single error", async () => {
it("should collect all invalid usernames and return aggregated error message", async () => {

Copilot uses AI. Check for mistakes.
k9ert and others added 2 commits December 22, 2025 13:37
Address Copilot code review suggestions:
- Extract hardcoded error messages to ERROR_MESSAGES constant for maintainability and i18n
- Rename test description to better describe aggregated error behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Prefix error message with count for easier debugging (e.g. "3 Invalid username(s) found:")
- Add test for empty records input
- Add test for all-invalid usernames scenario

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@k9ert
Copy link

k9ert commented Dec 22, 2025

PR review also from Opus 4.5:

PR Review: #372 - Collect all invalid usernames in batch payment validation

Overall: ✅ LGTM with minor suggestions

This PR correctly addresses the feature request from issue #69 where batch payment validation stopped at the first invalid username, forcing users with 130+ recipients to upload multiple times to find all errors.


Code Changes Analysis

apps/dashboard/app/batch-payments/utils.ts

✅ Good:

  • Collects errors in validationErrors[] array instead of early return
  • Uses continue to keep processing remaining records
  • Clear error format with newlines for readability
  • Minimal, focused change

⚠️ Minor observation:
The amount <= 0 check silently skips records without adding to validation errors. This is existing behavior but creates inconsistency - users won't know which records were skipped for this reason. Not a blocker, just worth noting.

apps/dashboard/components/batch-payments/index.tsx

✅ Good:

  • whiteSpace: "pre-line" correctly preserves newlines in error modal
  • Minimal, targeted fix

apps/dashboard/test/unit/batch-payments-utils.test.ts

✅ Good:

  • Comprehensive test coverage for the main fix
  • Tests mixed valid/invalid, all valid, zero amounts, null wallet cases
  • Proper mock setup

⚠️ Missing test cases to consider:

  • Empty records array []
  • ALL usernames invalid (no valid ones)
  • Large batch (>100 records mentioned in issue comments)

Suggestions

  1. Consider adding error count summary:
    const errorMessage = Invalid username(s) found (${validationErrors.length}):\n\n${validationErrors.join("\n")}

  2. Test for all-invalid scenario:
    it("should return all errors when no usernames are valid", async () => {
    // ...
    })

  3. Possible future enhancement: The original issue mentioned >100 username errors and underscore issues - those appear to be backend issues not addressed here. Worth tracking separately.


Verdict

Approve - The PR correctly solves the stated problem with clean, well-tested code. The implementation matches the approach outlined in the issue comments. Edge case tests would be nice but not blocking.

@k9ert
Copy link

k9ert commented Dec 22, 2025

I've implemented with Claude AI the comments from Copilot in 85b01aa and its own improvements in 01763ac

@k9ert k9ert marked this pull request as ready for review December 24, 2025 09:48
Copilot AI review requested due to automatic review settings December 24, 2025 09:48
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Suggestion – Batch Payment Validation for Blink

3 participants