- 
                Notifications
    You must be signed in to change notification settings 
- Fork 3
feat: job to clean up pending old payments #1066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: job to clean up pending old payments #1066
Conversation
| Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughThis PR refactors client payment management by establishing a dedicated  Changes
 Sequence DiagramsequenceDiagram
    participant API as Payment API
    participant CPS as ClientPaymentService
    participant DB as Database
    participant Chronik as MultiBlockchainClient
    participant Cleanup as Cleanup Job<br/>(Scheduled)
    
    API->>CPS: generatePaymentId(address, amount)
    CPS->>DB: Check if address exists
    alt Address not registered
        DB-->>CPS: Address not found
        CPS->>Chronik: syncAndSubscribeAddresses
        Chronik-->>CPS: Subscribed
    end
    CPS->>DB: Create clientPayment (PENDING)
    DB-->>CPS: paymentId
    CPS-->>API: Return paymentId
    
    Note over Cleanup: Every CLIENT_PAYMENT_EXPIRATION_TIME<br/>(7 days)
    Cleanup->>CPS: cleanupExpiredClientPayments()
    CPS->>DB: Query PENDING payments<br/>older than cutoff
    DB-->>CPS: Expired payment records
    CPS->>DB: Delete expired payments
    CPS-->>Cleanup: Cleanup complete (logged)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes The changes span multiple heterogeneous modifications: a new service module with straightforward CRUD operations, job orchestration setup, and non-trivial Chronik service restructuring (initialization simplification and new public APIs). Most changes follow consistent patterns, but the Chronik refactoring and service migration require careful verification of moved responsibilities and renamed imports across multiple files. Possibly related PRs
 Suggested labels
 Suggested reviewers
 Poem
 Pre-merge checks and finishing touches✅ Passed checks (3 passed)
 ✨ Finishing touches
 🧪 Generate unit tests (beta)
 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
 🧰 Additional context used🧬 Code graph analysis (1)jobs/workers.ts (3)
 ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
 🔇 Additional comments (1)
 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (1)
scripts/docker-exec-shortcuts.sh (1)
52-77: Restore login shell invocation for database/node shells.
bash -c bash -lruns a plainbashbecause-lbecomes the$0placeholder, so users no longer get a login shell (profile/env setup is skipped). The same regression affectsnodeshellandrootnodeshell. Please switch back to invokingbash -ldirectly (or quote the-ccommand properly) to keep the expected environment.Apply this diff:
- eval "$base_command_db" bash -c bash -l "$@" + eval "$base_command_db" bash -l "$@" ... - eval "$base_command_node" bash -c bash -l + eval "$base_command_node" bash -l ... - eval "$base_command_node_root" bash -c bash -l + eval "$base_command_node_root" bash -l
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
- constants/index.ts(1 hunks)
- jobs/initJobs.ts(2 hunks)
- jobs/workers.ts(2 hunks)
- pages/api/payments/paymentId/index.ts(1 hunks)
- scripts/docker-exec-shortcuts.sh(2 hunks)
- services/chronikService.ts(4 hunks)
- services/clientPaymentService.ts(1 hunks)
- services/transactionService.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
jobs/workers.ts (5)
services/chronikService.ts (1)
multiBlockchainClient(1114-1114)services/transactionService.ts (1)
connectAllTransactionsToPrices(474-487)redis/clientInstance.ts (1)
redisBullMQ(54-54)constants/index.ts (1)
DEFAULT_WORKER_LOCK_DURATION(177-177)services/clientPaymentService.ts (1)
cleanupExpiredClientPayments(60-82)
services/chronikService.ts (1)
types/chronikTypes.ts (1)
SyncAndSubscriptionReturn(50-53)
jobs/initJobs.ts (3)
redis/clientInstance.ts (1)
redisBullMQ(54-54)jobs/workers.ts (2)
syncBlockchainAndPricesWorker(34-63)
cleanupClientPaymentsWorker(65-86)constants/index.ts (1)
CLIENT_PAYMENT_EXPIRATION_TIME(295-295)
services/clientPaymentService.ts (4)
utils/validators.ts (1)
parseAddress(31-48)constants/index.ts (2)
NETWORK_IDS_FROM_SLUGS(132-135)
CLIENT_PAYMENT_EXPIRATION_TIME(295-295)services/addressService.ts (1)
addressExists(127-141)services/chronikService.ts (1)
multiBlockchainClient(1114-1114)
🪛 GitHub Actions: Pull Request Tests
services/clientPaymentService.ts
[error] 54-54: PrismaClientInitializationError: The provided database string is invalid. Error parsing connection string: invalid port number in database URL.
🪛 Shellcheck (0.11.0)
scripts/docker-exec-shortcuts.sh
[warning] 52-52: eval negates the benefit of arrays. Drop eval to preserve whitespace/symbols (or eval as string).
(SC2294)
🔇 Additional comments (9)
jobs/workers.ts (2)
4-6: LGTM!The new imports are correctly wired to support the blockchain sync and client payment cleanup workers.
65-86: LGTM!The cleanup worker is well-structured with proper error handling and logging.
services/clientPaymentService.ts (3)
46-51: LGTM!The status update function is straightforward and correctly delegates error handling to callers.
60-82: LGTM!The cleanup function is efficient and well-structured:
- Minimizes data transfer by selecting only
paymentId- Uses bulk delete operation
- Provides clear logging for observability
53-58: Invalid DATABASE_URL in CI config — not a code issue. Ensure your CI pipeline setsMAIN_DB_USER,MAIN_DB_PASSWORD,MAIN_DB_HOST,MAIN_DB_PORT, andMAIN_DB_NAME(e.g., via GitHub Secrets) soDATABASE_URLresolves correctly.services/chronikService.ts (4)
13-18: LGTM!The import changes correctly reflect the relocation of client payment functions to the new service module.
Note: Line 13 imports
getSimplifiedTrasaction(typo in function name), but this is a pre-existing issue not introduced by this PR.
860-876: LGTM!The sync method properly handles errors with logging and continues execution, which is appropriate for background synchronization tasks.
960-968: LGTM!The constructor refactoring simplifies initialization by collecting all async operations and awaiting them together before marking as initialized. This is a cleaner approach.
1080-1086: LGTM!The parallel synchronization of both networks is efficient and correctly waits for initialization before proceeding.
| worker.on('completed', (job) => { | ||
| // teardown | ||
| void (async () => { | ||
| console.log('Cleaning up MultiBlockchainClient global instance...') | ||
| await multiBlockchainClient.destroy() | ||
| console.log('Done.') | ||
| console.log(`job ${job.id as string}: blockchain + prices sync finished`) | ||
| })() | ||
| }) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for destroy() call.
The destroy() call is fire-and-forget with no error handling. If teardown fails, resources (e.g., WebSocket connections) may leak.
Apply this diff to add error handling:
   worker.on('completed', (job) => {
-    // teardown
     void (async () => {
-      console.log('Cleaning up MultiBlockchainClient global instance...')
-      await multiBlockchainClient.destroy()
-      console.log('Done.')
-      console.log(`job ${job.id as string}: blockchain + prices sync finished`)
+      try {
+        console.log('Cleaning up MultiBlockchainClient global instance...')
+        await multiBlockchainClient.destroy()
+        console.log(`job ${job.id as string}: blockchain + prices sync finished — cleanup done.`)
+      } catch (err: any) {
+        console.error(`job ${job.id as string}: cleanup FAILED — ${err.message}`)
+      }
     })()
   })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| worker.on('completed', (job) => { | |
| // teardown | |
| void (async () => { | |
| console.log('Cleaning up MultiBlockchainClient global instance...') | |
| await multiBlockchainClient.destroy() | |
| console.log('Done.') | |
| console.log(`job ${job.id as string}: blockchain + prices sync finished`) | |
| })() | |
| }) | |
| worker.on('completed', (job) => { | |
| void (async () => { | |
| try { | |
| console.log('Cleaning up MultiBlockchainClient global instance...') | |
| await multiBlockchainClient.destroy() | |
| console.log(`job ${job.id as string}: blockchain + prices sync finished — cleanup done.`) | |
| } catch (err: any) { | |
| console.error(`job ${job.id as string}: cleanup FAILED — ${err.message}`) | |
| } | |
| })() | |
| }) | 
🤖 Prompt for AI Agents
In jobs/workers.ts around lines 48 to 56, the async teardown invoked on worker
'completed' fire-and-forgets multiBlockchainClient.destroy() with no error
handling; wrap the await multiBlockchainClient.destroy() in a try/catch inside
the async IIFE (or convert to a named async function) so any thrown errors are
caught and logged (processLogger or console.error) and do not crash the worker,
and optionally perform a fallback/force cleanup in the catch; ensure the
function still resolves after logging the error.
| address = parseAddress(address) | ||
| const prefix = address.split(':')[0].toLowerCase() | ||
| const networkId = NETWORK_IDS_FROM_SLUGS[prefix] | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate networkId before database operation.
If the address prefix is not in NETWORK_IDS_FROM_SLUGS, networkId will be undefined, causing a database constraint violation when creating the clientPayment.
Apply this diff to add validation:
   address = parseAddress(address)
   const prefix = address.split(':')[0].toLowerCase()
   const networkId = NETWORK_IDS_FROM_SLUGS[prefix]
+  if (networkId === undefined) {
+    throw new Error(`Invalid network prefix: ${prefix}`)
+  }
   const isAddressRegistered = await addressExists(address)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| address = parseAddress(address) | |
| const prefix = address.split(':')[0].toLowerCase() | |
| const networkId = NETWORK_IDS_FROM_SLUGS[prefix] | |
| address = parseAddress(address) | |
| const prefix = address.split(':')[0].toLowerCase() | |
| const networkId = NETWORK_IDS_FROM_SLUGS[prefix] | |
| if (networkId === undefined) { | |
| throw new Error(`Invalid network prefix: ${prefix}`) | |
| } | |
| const isAddressRegistered = await addressExists(address) | 
🤖 Prompt for AI Agents
In services/clientPaymentService.ts around lines 14 to 16, the computed
networkId may be undefined when the address prefix isn't found in
NETWORK_IDS_FROM_SLUGS which will cause a DB constraint violation; after
computing const networkId = NETWORK_IDS_FROM_SLUGS[prefix], validate that
networkId is defined and if not, immediately throw or return a clear validation
error (e.g., BadRequestError or similar used in the codebase) that includes the
invalid prefix/address, and stop further processing so the create clientPayment
DB call never runs with an undefined networkId.
| if (!isAddressRegistered) { | ||
| void multiBlockchainClient.syncAndSubscribeAddresses([clientPayment.address]) | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for fire-and-forget synchronization.
The syncAndSubscribeAddresses call is fire-and-forget. If synchronization fails silently, the payment won't be tracked properly, but the function returns successfully.
Apply this diff to add error handling:
   if (!isAddressRegistered) {
-    void multiBlockchainClient.syncAndSubscribeAddresses([clientPayment.address])
+    multiBlockchainClient.syncAndSubscribeAddresses([clientPayment.address])
+      .catch(err => console.error(`[CLIENT_PAYMENT] Failed to sync address ${clientPayment.address.address}: ${err.message}`))
   }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!isAddressRegistered) { | |
| void multiBlockchainClient.syncAndSubscribeAddresses([clientPayment.address]) | |
| } | |
| if (!isAddressRegistered) { | |
| multiBlockchainClient.syncAndSubscribeAddresses([clientPayment.address]) | |
| .catch(err => console.error(`[CLIENT_PAYMENT] Failed to sync address ${clientPayment.address.address}: ${err.message}`)) | |
| } | 
🤖 Prompt for AI Agents
In services/clientPaymentService.ts around lines 39 to 41, the fire-and-forget
call to multiBlockchainClient.syncAndSubscribeAddresses may fail silently;
change it to handle errors by either awaiting the promise inside a try/catch or
attaching a .catch handler that logs the error (and optionally records a metric
or retries). Specifically, replace the void call with an awaited call inside a
try { await
multiBlockchainClient.syncAndSubscribeAddresses([clientPayment.address]) } catch
(err) { logger.error("Failed to sync/subscribe address", { address:
clientPayment.address, error: err }) } or, if you must remain non-blocking, call
multiBlockchainClient.syncAndSubscribeAddresses(...).catch(err =>
logger.error(...)) so failures are surfaced and retriable/monitored.
0bc8fba    to
    7c43b62      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Creates a recurrent job to clean up pending client payments older than 7 days, while refactoring client payment handling into a dedicated service and improving blockchain client lifecycle management.
- Extracts client payment functions into a dedicated service (clientPaymentService.ts)
- Adds a cleanup job that removes PENDINGpayments older than 7 days
- Refactors blockchain initialization to support explicit sync and teardown operations
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description | 
|---|---|
| services/clientPaymentService.ts | New service containing client payment operations and cleanup functionality | 
| services/transactionService.ts | Removes client payment functions that were moved to the new service | 
| services/chronikService.ts | Updates imports and adds explicit sync/destroy methods for better lifecycle management | 
| jobs/workers.ts | Adds new workers for blockchain sync and client payment cleanup | 
| jobs/initJobs.ts | Initializes the new background jobs with appropriate scheduling | 
| constants/index.ts | Defines the 7-day expiration time constant for client payments | 
| pages/api/payments/paymentId/index.ts | Updates import to use new client payment service | 
| tests/unittests/handleUpdateClientPaymentStatus.test.ts | Updates test mocks to reference new service | 
| scripts/docker-exec-shortcuts.sh | Fixes shell command syntax for container access | 
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| ;; | ||
| "databaseshell" | "dbs") | ||
| eval "$base_command_db" bash -l "$@" | ||
| eval "$base_command_db" bash -c bash -l "$@" | 
    
      
    
      Copilot
AI
    
    
    
      Oct 18, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bash command syntax is incorrect. It should be bash -l without the -c flag, or if using -c, it should execute a proper command string.
| eval "$base_command_db" bash -c bash -l "$@" | |
| eval "$base_command_db" bash -l "$@" | 
| ;; | ||
| "rootnodeshell" | "rns") | ||
| eval "$base_command_node_root" bash -l | ||
| eval "$base_command_node_root" bash -c bash -l | 
    
      
    
      Copilot
AI
    
    
    
      Oct 18, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bash command syntax is incorrect. It should be bash -l without the -c flag, or if using -c, it should execute a proper command string.
| eval "$base_command_node_root" bash -c bash -l | |
| eval "$base_command_node_root" bash -l | 
| }) | ||
|  | ||
| worker.on('failed', (job, err) => { | ||
| console.error(`[CLIENT_PAYMENT CLEANUP] job ${job?.id as string}: FAILED — ${err.message}`) | 
    
      
    
      Copilot
AI
    
    
    
      Oct 18, 2025 
    
  
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using as string on a potentially undefined value can cause runtime errors. The job parameter can be null/undefined in failed events.
| console.error(`[CLIENT_PAYMENT CLEANUP] job ${job?.id as string}: FAILED — ${err.message}`) | |
| console.error(`[CLIENT_PAYMENT CLEANUP] job ${(job?.id ?? "unknown")}: FAILED — ${err.message}`) | 
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
e4debef    to
    5690c86      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
- services/chronikService.ts(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
services/chronikService.ts (1)
types/chronikTypes.ts (1)
SyncAndSubscriptionReturn(50-53)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Tests
🔇 Additional comments (3)
services/chronikService.ts (3)
6-18: LGTM! Import restructuring aligns with service separation.The import changes correctly reflect the extraction of client payment logic into
clientPaymentService, supporting the PR's goal of adding client payment cleanup functionality.
1080-1086: LGTM! Clean implementation of missed transaction sync.The method correctly waits for initialization via
waitForStart()before syncing both blockchain clients in parallel. This provides a clean public API for the new sync job functionality.
1093-1104: LGTM! Proper error handling in cleanup method.The
destroy()method correctly handles connection cleanup with error logging, addressing the previous review concern. The try-catch ensures that failures in closing one client's connections don't prevent cleanup of others.
The merge-base changed after approval.
Related to #
Description
Creates a recurrent job that will clean
ClientPayments older than 7 days with the status'PENDING'.Test plan
Create some client payments, change the constant at the end of
constants/indexto a smaller time (instead of a week, use e.g.10_000for 10 seconds). Make sure payments are deleted as expectedSummary by CodeRabbit