Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Oct 28, 2025

Summary by CodeRabbit

  • Bug Fixes
    • More accurate available balance calculation for withdrawals by separately accounting for pending invoices and processed payouts.
  • Improvements
    • Improved logging to surface amounts held for pending invoices and processed payouts used in withdrawal decisions.
  • Bug Fixes
    • Webhook payout processing now uses a presence check and skips a specific test invoice to avoid accidental processing.

@vercel
Copy link
Contributor

vercel bot commented Oct 28, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 28, 2025 4:43am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

Replaces a combined payout aggregation with separate aggregates for processing invoices (older than 3 days) and processed payouts, subtracting both from Stripe's available balance to compute withdrawable funds; also adds a count-based guard and test-invoice early return in the charge-succeeded webhook flow.

Changes

Cohort / File(s) Summary
Withdrawal balance calculation refactor
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts
Replace combined payout aggregation with two aggregates: processingInvoices (invoices status "processing" older than 3 days) and processedPayouts (payouts status "processed"); compute processingInvoicesAmount, processedPayoutsAmount, amountToKeepOnStripe; set balanceToWithdraw = currentAvailableBalance - amountToKeepOnStripe; update logging and retain reserved/payout creation flow.
Charge-succeeded webhook guard & logging
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts
Add import of log; change payout lookup from findMany to count and use payoutsToProcess === 0 as the guard; add early-return for a specific test invoice ID with a skip log; adjust log messages accordingly; keep publish-to-Qstash flow.

Sequence Diagram(s)

sequenceDiagram
    participant Cron as Withdrawal Cron
    participant Stripe as Stripe API
    participant DB as Prisma (invoices/payouts)
    participant PayoutSvc as Payout creation

    Note over Cron,Stripe: Trigger cron job
    Cron->>Stripe: fetch balance (available)
    Cron->>DB: aggregate invoices where status="processing" and createdAt < now-3d
    Cron->>DB: aggregate payouts where status="processed"
    DB-->>Cron: processingInvoicesAmount, processedPayoutsAmount
    Cron->>Cron: amountToKeepOnStripe = processingInvoicesAmount + processedPayoutsAmount
    Cron->>Cron: balanceToWithdraw = available - amountToKeepOnStripe
    alt balanceToWithdraw > threshold
        Cron->>PayoutSvc: create payout
        PayoutSvc-->>Cron: payout created
    else
        Cron-->>Cron: no payout created
    end
Loading
sequenceDiagram
    participant Webhook as Stripe webhook handler
    participant DB as Prisma (payouts)
    participant Q as Qstash publisher

    Webhook->>Webhook: receive charge.succeeded invoice
    alt invoice.id == TEST_INVOICE_ID
        Webhook-->>Webhook: log skip and return
    else
        Webhook->>DB: count payouts to process for invoice
        DB-->>Webhook: payoutsToProcess
        alt payoutsToProcess == 0
            Webhook-->>Webhook: log "no payouts to process" and return
        else
            Webhook->>Q: publish payout job
            Q-->>Webhook: queued
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to the 3-day invoice threshold and its intent in trigger-withdrawal/route.ts
  • Verify test-invoice early-return ID and ensure it won't skip real invoices
  • Confirm logging accurately reflects new aggregated fields and amounts

Possibly related PRs

  • Fix withdrawal cron again #3016 — Similar edits to trigger-withdrawal/route.ts changing how processing/processed amounts are aggregated and used for withdrawal calculation.
  • Update trigger-withdrawal cron #2494 — Prior change that replaced payouts findMany with aggregation-based sums in the withdrawal cron (related aggregation work).
  • Fix withdrawal cron #2544 — Another related change that recalculates reserved amounts and withdrawable balance in the same cron route.

Suggested reviewers

  • devkiran

Poem

🐇 I hopped through sums and numbers bright,
I counted invoices in the night,
Kept some funds on Stripe, let others go,
A little hop, a careful flow —
Withdrawal logic snug and light.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "Fix cron/withdrawal again" is partially related to the changeset, as it correctly identifies the primary file being modified (the withdrawal cron route). However, the title lacks specificity about what is actually being fixed. It uses generic language ("Fix... again") without explaining the actual nature of the changes, such as the separation of invoice and payout aggregates or the balance calculation adjustments. Additionally, the changeset includes modifications to a second file (charge-succeeded.ts) that is not mentioned in the title, though the withdrawal cron changes appear to be the focus. The vague phrasing makes it difficult for someone scanning commit history to understand the substantive nature of the fix. Consider revising the title to be more descriptive and specific about what is being fixed. For example, a title like "Fix withdrawal balance calculation by separating invoice and payout aggregates" or "Refactor withdrawal cron balance logic for accuracy" would better convey the actual changes and be more useful for code history scanning. This would help reviewers and future readers understand the intent of the fix without requiring them to read the full PR description.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-withdrawal-again

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (2)

28-28: Optimize date calculation for clarity and efficiency.

The nested new Date() calls create unnecessary objects. Consider refactoring for better readability and minor performance improvement.

Apply this diff:

-              lt: new Date(new Date().setDate(new Date().getDate() - 3)),
+              lt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000),

Or for better clarity:

+            const threeDaysAgo = new Date();
+            threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
+
             createdAt: {
               // Why we're doing this: ACH payments usually take up to 4 business days to settle
               // but in case it settles earlier, we'd want to keep the balance on Stripe for payouts
               // So, we're including "processing" invoices created more than 3 days ago in the reserve balance as well
-              lt: new Date(new Date().setDate(new Date().getDate() - 3)),
+              lt: threeDaysAgo,
             },

35-52: Add comment explaining processed payouts reserve logic.

The comment on lines 25-27 explains why processing invoices are reserved, but there's no explanation for why processed payouts need to be kept on Stripe. This could confuse future developers.

Consider adding a comment to clarify the business logic:

     prisma.payout.aggregate({
       where: {
+        // Keep processed payouts in reserve because Stripe payouts can take time to complete
+        // and the funds may still be in the Stripe account even though marked "processed" in our DB
         status: "processed",
       },
       _sum: {
         amount: true,
       },
     }),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87a56ca and 47e15bb.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (2)
apps/web/lib/stripe/index.ts (1)
  • stripe (4-10)
packages/prisma/index.ts (1)
  • prisma (3-9)
⏰ 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: build
🔇 Additional comments (3)
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (3)

18-46: LGTM! Efficient parallel fetching with solid refactoring.

The refactoring to separate invoice and payout aggregation is clean and the use of Promise.all ensures efficient parallel execution. The logic correctly distinguishes between processing invoices and processed payouts for reserve calculation.

Minor note: Lines 45-46 access stripeBalanceData.available[0] and stripeBalanceData.pending[0] without bounds checking. While Stripe always returns at least one balance object, consider adding a runtime check or assertion for defensive programming.


48-52: LGTM! Proper null safety handling.

The nullish coalescing operator (?? 0) correctly handles cases where aggregates return null (when no records match), preventing potential runtime errors.


72-75: Note: Currency is hardcoded to USD.

The payout creation assumes USD currency, which aligns with accessing stripeBalanceData.available[0] on line 45. This is fine for current requirements, but be aware that adding multi-currency support would require refactoring this logic to handle multiple balances.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47e15bb and 64b9c68.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (1)
packages/prisma/index.ts (1)
  • prisma (3-9)
⏰ 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: build
🔇 Additional comments (2)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (2)

7-7: LGTM!

The log import is necessary for the new invoice guard and follows the existing import pattern.


57-68: Nice optimization!

Replacing findMany with count is more efficient since only the count is needed to determine whether to proceed. The updated log message is also more descriptive.

@steven-tey steven-tey merged commit 31d5d18 into main Oct 28, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the fix-withdrawal-again branch October 28, 2025 04:59
@panda-sandeep
Copy link

/bug0 run

2 similar comments
@panda-sandeep
Copy link

/bug0 run

@panda-sandeep
Copy link

/bug0 run

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants