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

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Dec 5, 2025

Summary by CodeRabbit

  • Refactor
    • Improved payout completion workflow reliability through asynchronous task processing.
    • Payout status updates and partner notifications now handled through a more resilient queuing mechanism, maintaining existing functionality while enhancing system stability.

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

@vercel
Copy link
Contributor

vercel bot commented Dec 5, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Dec 5, 2025 5:05pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Caution

Review failed

The head commit changed during the review from b107c4d to 157ed16.

Walkthrough

This PR refactors Stripe payout completion processing from synchronous webhook handling to asynchronous QStash-based queue processing. The webhook now enqueues payout events to a cron endpoint, which performs database updates and sends notifications.

Changes

Cohort / File(s) Summary
Payout Processing Queue Refactor
apps/web/app/(ee)/api/cron/payouts/payout-paid/route.ts, apps/web/app/(ee)/api/stripe/connect/webhook/payout-paid.ts
Added new cron POST handler to process dequeued payout-paid events: validates Qstash signatures, looks up partner by stripeConnectId, updates payout records from "sent" to "completed", and sends notification emails. Webhook refactored to enqueue POST requests to the cron endpoint instead of performing direct DB/email operations.

Sequence Diagram

sequenceDiagram
    actor Stripe
    participant Webhook as Webhook Handler<br/>(payout-paid)
    participant QStash as QStash Queue
    participant Cron as Cron Handler<br/>(payout-paid)
    participant DB as Database
    participant Email as Email Service

    Stripe->>Webhook: POST /api/stripe/connect/webhook/payout-paid
    activate Webhook
    Webhook->>QStash: Enqueue POST to /api/cron/payouts/payout-paid<br/>(stripeAccount, stripePayout)
    QStash-->>Webhook: messageId
    Webhook-->>Stripe: 200 OK
    deactivate Webhook

    QStash->>Cron: POST /api/cron/payouts/payout-paid<br/>(payload + signature)
    activate Cron
    Cron->>Cron: Validate Qstash signature
    Cron->>DB: Lookup partner by stripeConnectId
    alt Partner found
        Cron->>DB: Update payout records<br/>(status: sent → completed)
        alt Partner has email
            Cron->>Email: Send notification email<br/>(payout template)
            Email-->>Cron: Email sent
        end
        Cron-->>QStash: 200 OK (summary)
    else Partner not found
        Cron-->>QStash: Error response
    end
    deactivate Cron
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • New cron handler: Requires careful review of Qstash signature validation, partner lookup logic, payout status update conditions, and email notification integration
  • Webhook refactoring: Verify proper queue integration and that all required payload details (stripeAccount, stripePayout) are correctly forwarded
  • Error handling: Ensure comprehensive error logging and appropriate responses across both endpoints
  • Integration points: Confirm the deduplicationId usage (event.id) prevents duplicate processing

Possibly related PRs

Suggested reviewers

  • steven-tey

Poem

🐰 Hops with glee

Payouts now queue up so neat,
No more synchronous heartbeat!
Stripe sends word, we enqueue fast,
Cron picks up—side-effects at last. 📬
Async dreams, a rabbit's delight! 🌙

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 pull request title clearly and accurately describes the main change: refactoring the 'payout-paid' webhook to use a queue-based approach instead of direct operations.

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.

@devkiran devkiran requested a review from steven-tey December 5, 2025 15:31
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 bed2391 and 6a8ad5c.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/payouts/payout-paid/route.ts (1 hunks)
  • apps/web/app/(ee)/api/stripe/connect/webhook/payout-paid.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.
📚 Learning: 2025-06-19T01:46:45.723Z
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.

Applied to files:

  • apps/web/app/(ee)/api/cron/payouts/payout-paid/route.ts
  • apps/web/app/(ee)/api/stripe/connect/webhook/payout-paid.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/payout-paid.ts
📚 Learning: 2025-06-25T21:20:59.837Z
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.

Applied to files:

  • apps/web/app/(ee)/api/stripe/connect/webhook/payout-paid.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/payouts/payout-paid/route.ts (5)
packages/prisma/index.ts (1)
  • prisma (3-9)
packages/email/src/index.ts (1)
  • sendEmail (6-29)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
packages/email/src/templates/partner-payout-withdrawal-completed.tsx (1)
  • PartnerPayoutWithdrawalCompleted (18-112)
apps/web/lib/api/errors.ts (1)
  • handleAndReturnErrorResponse (162-165)
⏰ 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 (8)
apps/web/app/(ee)/api/stripe/connect/webhook/payout-paid.ts (3)

1-7: LGTM! Queue setup looks correct.

The queue initialization and imports are properly configured. Using APP_DOMAIN_WITH_NGROK enables local development testing with ngrok, which is a good practice.


19-33: Good use of deduplication with event.id.

The enqueue logic properly uses event.id as the deduplicationId, which ensures that duplicate Stripe webhook events won't result in duplicate processing. The payload structure is clean and contains all necessary fields.

Note: The absence of error handling here means that if enqueueJSON fails, the webhook handler will throw an error, causing Stripe to retry the webhook. This is typically the desired behavior for webhook reliability.


35-35: Clear return message for webhook acknowledgment.

The return message appropriately indicates the job was enqueued and includes the messageId for tracking.

apps/web/app/(ee)/api/cron/payouts/payout-paid/route.ts (5)

1-9: Imports look comprehensive.

All necessary dependencies are imported for handling the cron job processing.


10-19: Payload schema correctly matches webhook payload.

The Zod schema accurately reflects the structure sent by the webhook handler, including the nullable traceId field.


22-44: Proper authentication and validation flow.

The handler correctly verifies the QStash signature before processing, ensuring only legitimate requests from QStash are accepted. The partner lookup is efficient, selecting only the needed field, and gracefully handles missing partners.


78-89: Error handling looks solid.

The error handling properly logs failures and returns appropriate error responses using the shared error utility. The success message includes useful debugging information.


46-55: The updateMany query correctly uses both stripePayoutId and stripePayoutTraceId as a composite matching key. This design is intentional: traceId can be null (per the Stripe webhook), and Prisma correctly handles IS NULL matching in such cases. The combination of both fields ensures accurate payout matching even though each field individually is nullable. No changes needed.

@steven-tey steven-tey merged commit 2048d1b into main Dec 5, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the payout-paid-queue branch December 5, 2025 18:03
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