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

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Nov 27, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of pending payouts to detect and process cases where payouts may be stuck, enabling better recovery mechanisms
    • Enhanced payout workflow with automatic balance availability verification for improved transaction completion

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Nov 27, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 27, 2025 5:40am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

Walkthrough

The account-updated Stripe webhook now integrates QStash queue messaging to handle pending payouts. When payout updates occur, the system checks for pending "sent" internal payouts and enqueues balance availability processing. A test retry interval is increased from 300ms to 600ms.

Changes

Cohort / File(s) Summary
Stripe webhook payout queue integration
apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts
Imports QStash and creates "withdraw-stripe-balance" queue. Adds logic to detect pending "sent" internal payouts after payout updates and enqueues a request to /api/cron/payouts/balance-available with Stripe account ID and event ID for deduplication.
Test timing adjustment
apps/web/tests/fraud/index.test.ts
Increases retry interval in waitForFraudEvent from 300ms to 600ms.

Sequence Diagram(s)

sequenceDiagram
    participant Stripe
    participant Webhook as account-updated webhook
    participant QStash
    participant CronAPI as /api/cron/payouts/balance-available
    
    Stripe->>Webhook: POST payout update event
    Webhook->>Webhook: Process payout updates
    Webhook->>Webhook: Query pending payouts (status="sent", mode="internal")
    alt Pending payouts exist
        Webhook->>QStash: Enqueue balance-available request<br/>(deduplicationId: Stripe event ID)
        QStash->>CronAPI: Trigger payout processing
        CronAPI->>CronAPI: Handle pending payouts
    end
    Webhook->>Stripe: Return success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Account-updated webhook changes: Requires understanding QStash queue mechanics, the payout status model (sent/internal modes), deduplication semantics, and integration with the cron endpoint. The logic is straightforward but introduces new async messaging concerns.
  • Test adjustment: Minimal logic change; verify the timing adjustment doesn't mask race conditions in fraud event detection.

Possibly related PRs

Suggested reviewers

  • steven-tey

Poem

🐰 A webhook hops to QStash's queue,
Pending payouts finally get their due,
With dedup IDs and cron jobs true,
Balance flows freeβ€”no more stuck queue! πŸ’³

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (2 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately reflects the main changes: adding automatic retry logic for Stripe payouts when stuck payouts are detected after account verification events.
✨ 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 automatically-retry-payout

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: 0

🧹 Nitpick comments (1)
apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts (1)

134-134: Consider adding non-null assertion for consistency.

Line 51 uses partner.stripeConnectId! but this line omits it. While the value is guaranteed to exist (since the partner was found by this field), adding the assertion would be consistent with the rest of the file.

       body: {
-        stripeAccount: partner.stripeConnectId,
+        stripeAccount: partner.stripeConnectId!,
       },
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between beac3b7 and 80cd668.

πŸ“’ Files selected for processing (2)
  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts (2 hunks)
  • apps/web/tests/fraud/index.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
πŸ““ Common learnings
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-19T01:46:45.723Z
Learning: PayPal webhook verification in the Dub codebase is handled at the route level in `apps/web/app/(ee)/api/paypal/webhook/route.ts` using the `verifySignature` function. Individual webhook handlers like `payoutsItemFailed` don't need to re-verify signatures since they're only called after successful verification.
Learnt from: devkiran
Repo: dubinc/dub PR: 2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-06-25T21:20:59.837Z
Learning: In the Dub codebase, payout limit validation uses a two-stage pattern: server actions perform quick sanity checks (payoutsUsage > payoutsLimit) for immediate user feedback, while the cron job (/cron/payouts) performs authoritative validation (payoutsUsage + payoutAmount > payoutsLimit) with actual calculated amounts before processing. This design provides fast user feedback while ensuring accurate limit enforcement at transaction time.
πŸ“š Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/tests/fraud/index.test.ts
πŸ“š Learning: 2025-08-25T21:41:06.073Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2758
File: apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts:43-45
Timestamp: 2025-08-25T21:41:06.073Z
Learning: For Stripe API calls on connected accounts, the stripeAccount parameter should be passed in the first parameter object (e.g., stripe.balance.retrieve({ stripeAccount })), not as request options in the second parameter.

Applied to files:

  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts
πŸ“š Learning: 2025-07-11T16:28:55.693Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.693Z
Learning: In the Dub codebase, multiple payout records can now share the same stripeTransferId because payouts are grouped by partner and processed as single Stripe transfers. This is why the unique constraint was removed from the stripeTransferId field in the Payout model - a single transfer can include multiple payouts for the same partner.

Applied to files:

  • apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.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 (3)
apps/web/tests/fraud/index.test.ts (1)

257-257: Reasonable timing adjustment.

Doubling the retry interval from 300ms to 600ms (total max wait from 3s to 6s) should help reduce test flakiness, especially with the new async queue-based payout processing introduced in this PR.

apps/web/app/(ee)/api/stripe/connect/webhook/account-updated.ts (2)

8-10: LGTM!

Module-level queue initialization is appropriate for reuse across webhook invocations.


118-137: Solid implementation for retrying stuck payouts.

The logic correctly:

  • Only triggers when there are pending payouts (status: "sent", mode: "internal")
  • Uses event.id as deduplicationId for idempotency, preventing duplicate queue entries on webhook retries

Letting queue failures propagate (rather than catching them) is reasonable here since Stripe will retry the webhook, and the deduplicationId ensures no duplicates on success.

@steven-tey steven-tey merged commit c0d036f into main Nov 27, 2025
8 of 9 checks passed
@steven-tey steven-tey deleted the automatically-retry-payout branch November 27, 2025 07:50
This was referenced Dec 5, 2025
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.

3 participants