-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Refactor Stripe payout to use QStash workflow #3037
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughRemoved legacy in-file payout handlers; introduced batched QStash/Upstash routes and a create-stripe-transfer workflow; extracted invoice-driven payout and domain-renewal processors; consolidated workflow config and payload schemas. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Stripe as Stripe webhook (charge-succeeded)
participant DB as Database (Invoice)
participant QStash as QStash
participant SendStripe as /cron/payouts/send-stripe-payouts
participant SendPayPal as /cron/payouts/send-paypal-payouts
participant Trigger as triggerWorkflows
participant CreateWF as create-stripe-transfer workflow
participant StripeAPI as Stripe API
participant PayPalAPI as PayPal API
participant Email as Email service
Stripe->>DB: update invoice (receipt, status, paidAt, metadata)
DB-->>Stripe: ack
alt invoice.type == partnerPayout
Stripe->>QStash: enqueue processPayoutInvoice(invoiceId)
QStash->>SendStripe: POST invoiceId (opt. startingAfter)
SendStripe->>DB: load batch of processing payouts
SendStripe->>Trigger: triggerWorkflows(per-partner payloads)
Trigger->>CreateWF: run workflow per partner
CreateWF->>DB: validate partner & load payouts
CreateWF->>StripeAPI: create transfer(s)
StripeAPI-->>CreateWF: transfer result
CreateWF->>DB: update payouts & commissions (updateMany)
CreateWF->>Email: send PartnerPayoutProcessed
SendStripe->>QStash: schedule next batch (if more)
else invoice.type == domainRenewal
Stripe->>QStash: enqueue processDomainRenewal(invoiceId)
QStash->>DB: load domains, update expiresAt
QStash->>Email: send DomainRenewed to owners
end
alt PayPal path (from processPayoutInvoice)
QStash->>SendPayPal: POST invoiceId
SendPayPal->>PayPalAPI: create PayPal batch payout
PayPalAPI-->>SendPayPal: batch result
SendPayPal->>DB: update payouts to sent
SendPayPal->>Email: send PayPal notifications
end
Estimated code review effortπ― 4 (Complex) | β±οΈ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touchesβ Passed checks (2 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
π Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro π Files selected for processing (1)
π§ Files skipped from review as they are similar to previous changes (1)
β° 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)
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: 2
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (8)
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/route.ts(1 hunks)apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts(2 hunks)apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts(1 hunks)apps/web/app/(ee)/api/workflows/partner-approved/route.ts(3 hunks)apps/web/lib/cron/qstash-workflow.ts(1 hunks)apps/web/lib/partners/create-stripe-transfer.ts(1 hunks)apps/web/lib/zod/schemas/partners.ts(1 hunks)apps/web/lib/zod/schemas/payouts.ts(1 hunks)
π§° Additional context used
π§ Learnings (5)
π Common learnings
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-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/lib/partners/create-stripe-transfer.tsapps/web/lib/zod/schemas/payouts.tsapps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.tsapps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts
π Learning: 2025-10-17T08:18:19.278Z
Learnt from: devkiran
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2025-10-17T08:18:19.278Z
Learning: In the apps/web codebase, `@/lib/zod` should only be used for places that need OpenAPI extended zod schema. All other places should import from the standard `zod` package directly using `import { z } from "zod"`.
Applied to files:
apps/web/lib/zod/schemas/partners.ts
π Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
Applied to files:
apps/web/lib/zod/schemas/partners.ts
π Learning: 2025-08-25T17:33:45.072Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts:12-12
Timestamp: 2025-08-25T17:33:45.072Z
Learning: The WorkflowTrigger enum in packages/prisma/schema/workflow.prisma contains three values: leadRecorded, saleRecorded, and commissionEarned. All three are properly used throughout the codebase.
Applied to files:
apps/web/lib/cron/qstash-workflow.ts
𧬠Code graph analysis (6)
apps/web/lib/partners/create-stripe-transfer.ts (1)
packages/prisma/index.ts (1)
prisma(3-9)
apps/web/app/(ee)/api/workflows/partner-approved/route.ts (2)
apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts (1)
serve(11-136)apps/web/lib/zod/schemas/partners.ts (1)
partnerApprovedWorkflowSchema(760-764)
apps/web/lib/zod/schemas/payouts.ts (1)
apps/web/lib/zod/schemas/programs.ts (1)
ProgramSchema(23-49)
apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts (6)
apps/web/lib/zod/schemas/payouts.ts (1)
createStripeTransferWorkflowSchema(107-112)apps/web/lib/cron/qstash-workflow-logger.ts (1)
createWorkflowLogger(13-36)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/partners/create-stripe-transfer.ts (1)
createStripeTransfer(16-156)packages/email/src/index.ts (1)
sendBatchEmail(31-67)packages/email/src/templates/partner-payout-processed.tsx (1)
PartnerPayoutProcessed(17-119)
apps/web/lib/cron/qstash-workflow.ts (2)
apps/web/lib/zod/schemas/partners.ts (1)
partnerApprovedWorkflowSchema(760-764)apps/web/lib/zod/schemas/payouts.ts (1)
createStripeTransferWorkflowSchema(107-112)
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts (1)
apps/web/lib/cron/qstash-workflow.ts (2)
QStashWorkflow(33-36)triggerWorkflows(39-86)
π Additional comments (18)
apps/web/lib/partners/create-stripe-transfer.ts (3)
131-142: LGTM! Efficient batch update.Consolidating the payout updates into a single
updateManycall is more efficient than individual updates and ensures all payouts are updated atomically with the same transfer ID.
144-153: LGTM! Consistent batch update pattern.The commission batch update follows the same efficient pattern as the payout update, ensuring all related commissions are marked as paid in a single operation.
155-155: LGTM! Supports workflow integration.Returning the transfer object enables the new workflow-based architecture to access transfer details for downstream processing and notifications.
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/route.ts (1)
90-117: LGTM! Batching logic is well-structured.The recursive batching mechanism via QStash is correctly implemented. The error handling checks for
messageIdand logs failures appropriately. The payload includes only the necessaryinvoiceIdfor the next batch.apps/web/lib/zod/schemas/payouts.ts (1)
93-112: Well-designed workflow schema.The
payoutSchemaappropriately captures the minimal fields needed for the Stripe transfer workflow and email notifications. The separation ofpreviouslyProcessedPayoutsandcurrentInvoicePayoutsaligns with the business logic of grouping payouts by partner across invoices.apps/web/lib/zod/schemas/partners.ts (1)
760-764: LGTM! Clean workflow schema definition.The schema is minimal and appropriate for the partner approval workflow, capturing the essential identifiers needed.
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts (1)
1-4: LGTM! Imports correctly reflect the architectural shift.The import changes appropriately swap direct processing functions for the workflow-based approach using
QStashWorkflowandtriggerWorkflows.apps/web/app/(ee)/api/workflows/partner-approved/route.ts (3)
9-12: LGTM! Import consolidation improves maintainability.Moving from a local schema to the shared
partnerApprovedWorkflowSchemaimproves consistency and reduces duplication.
43-43: LGTM! Type inference ensures type safety.Using
z.infer<typeof partnerApprovedWorkflowSchema>ensures the route's payload type is always in sync with the schema definition.
332-332: LGTM! Parser correctly uses shared schema.The
initialPayloadParsernow uses the shared schema, maintaining consistency with the type definition on line 43.apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts (4)
1-8: LGTM! All necessary imports are present.The imports are well-organized and include all dependencies needed for the workflow implementation.
10-44: LGTM! Route setup follows best practices.The workflow route correctly uses typed
serve, creates a logger for observability, and fetches the partner once to be reused across steps.
46-75: LGTM! Comprehensive validation before transfer creation.Step 1 includes all necessary validation checks (partner exists, payouts enabled, stripeConnectId present) with appropriate error logging for each failure scenario.
130-134: LGTM! Parser follows standard workflow pattern.The
initialPayloadParsercorrectly validates the payload using the schema, ensuring type safety.apps/web/lib/cron/qstash-workflow.ts (4)
3-5: LGTM! Imports support the schema-driven approach.The new imports enable the config-driven workflow system with type-safe payload validation.
11-25: Excellent config-driven design!The centralized
WORKFLOW_CONFIGimproves maintainability by consolidating all workflow metadata in one place. Theas constassertion enables proper type inference for the mapped types below. Parallelism values are appropriately tuned (20 for partner-approved, 10 for create-stripe-transfer which involves external API calls).
27-36: Excellent type-safe design!The mapped type system ensures compile-time safety:
QStashWorkflow<T>constrains thebodyto match the specific workflow's payload type. This prevents mismatches between workflow IDs and their payloads.
40-58: LGTM! Config-driven approach improves maintainability.The refactored
triggerWorkflowsnow derives all settings fromWORKFLOW_CONFIG, eliminating hardcoded values. The type-safe signature ensures only valid workflow IDs and matching payloads can be passed.
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts
Outdated
Show resolved
Hide resolved
β¦ files; implement new routes for sending PayPal and Stripe payouts; update invoice processing logic to handle batch payouts and email notifications.
β¦processing into separate utility functions for improved organization and maintainability.
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: 6
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (9)
apps/web/app/(ee)/api/cron/campaigns/broadcast/route.ts(1 hunks)apps/web/app/(ee)/api/cron/payouts/charge-succeeded/route.ts(0 hunks)apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts(0 hunks)apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts(0 hunks)apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts(1 hunks)apps/web/app/(ee)/api/cron/payouts/send-paypal-payouts/route.ts(1 hunks)apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.ts(1 hunks)apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts(1 hunks)apps/web/lib/zod/schemas/payouts.ts(1 hunks)
π€ Files with no reviewable changes (3)
- apps/web/app/(ee)/api/cron/payouts/charge-succeeded/route.ts
- apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts
- apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts
β Files skipped from review due to trivial changes (1)
- apps/web/app/(ee)/api/cron/campaigns/broadcast/route.ts
π§° Additional context used
π§ Learnings (3)
π Common learnings
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/send-paypal-payouts/route.tsapps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.tsapps/web/app/(ee)/api/stripe/webhook/charge-succeeded.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/lib/zod/schemas/payouts.tsapps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts
𧬠Code graph analysis (3)
apps/web/app/(ee)/api/cron/payouts/send-paypal-payouts/route.ts (5)
apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.ts (1)
POST(26-160)packages/prisma/index.ts (1)
prisma(3-9)packages/email/src/index.ts (1)
sendBatchEmail(31-70)packages/email/src/templates/partner-payout-processed.tsx (1)
PartnerPayoutProcessed(17-119)apps/web/lib/api/errors.ts (1)
handleAndReturnErrorResponse(175-178)
apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.ts (4)
apps/web/app/(ee)/api/cron/payouts/send-paypal-payouts/route.ts (1)
POST(21-118)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/cron/qstash-workflow.ts (1)
triggerWorkflows(39-87)apps/web/lib/api/errors.ts (1)
handleAndReturnErrorResponse(175-178)
apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts (6)
apps/web/lib/zod/schemas/payouts.ts (1)
createStripeTransferWorkflowSchema(93-97)apps/web/lib/cron/qstash-workflow-logger.ts (1)
createWorkflowLogger(13-36)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/partners/create-stripe-transfer.ts (1)
createStripeTransfer(16-156)packages/email/src/index.ts (1)
sendBatchEmail(31-70)packages/email/src/templates/partner-payout-processed.tsx (1)
PartnerPayoutProcessed(17-119)
β° 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 (1)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (1)
73-86: LGTM! Good use of parallel execution.Using
Promise.allSettledis appropriate here since Stripe and PayPal payout processing are independent operations. The distinct deduplication IDs prevent duplicate processing per service.
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)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (1)
32-48: Invoice marked completed before processingβcreates inconsistent state on utility failure.The invoice is marked as
status: "completed"at line 38, but the actual payout/domain renewal processing happens afterward (lines 44-48). If eitherprocessPayoutInvoiceorprocessDomainRenewalInvoicethrows an error, the invoice remains marked as completed while the processing never finished, creating an inconsistent state.Consider one of these approaches:
- Move the status update after successful processing (preferred if utilities are synchronous or throw on failure):
- invoice = await prisma.invoice.update({ + const updatedInvoice = await prisma.invoice.update({ where: { id: invoice.id, }, data: { receiptUrl: charge.receipt_url, - status: "completed", paidAt: new Date(), stripeChargeMetadata: JSON.parse(JSON.stringify(charge)), }, }); - if (invoice.type === "partnerPayout") { - await processPayoutInvoice({ invoice }); - } else if (invoice.type === "domainRenewal") { - await processDomainRenewalInvoice({ invoice }); + if (updatedInvoice.type === "partnerPayout") { + await processPayoutInvoice({ invoice: updatedInvoice }); + } else if (updatedInvoice.type === "domainRenewal") { + await processDomainRenewalInvoice({ invoice: updatedInvoice }); } + + // Mark as completed only after successful processing + await prisma.invoice.update({ + where: { + id: updatedInvoice.id, + }, + data: { + status: "completed", + }, + });
Add explicit error handling to wrap utility calls and update invoice status based on outcome.
Use a "processing" intermediate status before "completed" to track the state more accurately.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (3)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts(1 hunks)apps/web/app/(ee)/api/stripe/webhook/utils/process-domain-renewal-invoice.ts(1 hunks)apps/web/app/(ee)/api/stripe/webhook/utils/process-payout-invoice.ts(1 hunks)
π§° Additional context used
π§ Learnings (1)
π Common learnings
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.
𧬠Code graph analysis (2)
apps/web/app/(ee)/api/stripe/webhook/utils/process-domain-renewal-invoice.ts (4)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/dynadot/set-renew-option.ts (1)
setRenewOption(12-61)packages/email/src/index.ts (1)
sendBatchEmail(31-70)apps/web/app/(ee)/api/stripe/webhook/utils/process-domain-renewal-failure.ts (1)
processDomainRenewalFailure(10-121)
apps/web/app/(ee)/api/stripe/webhook/utils/process-payout-invoice.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 (1)
44-48: Error handling is properly implemented at the route handler level.The
chargeSucceededfunction is called within a try-catch block in the parent route handler. If any error is thrown, it's caught and logged, then a Response with status 400 is returned, which properly triggers Stripe's retry mechanism.apps/web/app/(ee)/api/stripe/webhook/utils/process-payout-invoice.ts (1)
7-14: Verify payout status values.The filter excludes only
status: "completed", but the codebase defines additional terminal statuses. Specifically:
- "failed" payouts can only be retried manually (see
payout-row-menu.tsx:42andretry-failed-paypal-payouts.ts:70), so they should not be automatically requeued.- "canceled" payouts are defined in the PayoutStatus enum (
payout-status-badges.tsx) but should not be processed.Consider excluding these statuses from the query to prevent unnecessary queue operations and maintain consistency with the intended payout workflow.
apps/web/app/(ee)/api/stripe/webhook/utils/process-domain-renewal-invoice.ts
Show resolved
Hide resolved
apps/web/app/(ee)/api/stripe/webhook/utils/process-domain-renewal-invoice.ts
Show resolved
Hide resolved
apps/web/app/(ee)/api/stripe/webhook/utils/process-domain-renewal-invoice.ts
Show resolved
Hide resolved
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: 1
β»οΈ Duplicate comments (3)
apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts (1)
118-125: Email notification step will not find payouts after successful transfer.The query filters for
status: "processed"butcreateStripeTransfer()updates successful payouts to"sent", so this returns an empty result set on the happy path and partners never receive email notifications.Apply this diff to include both statuses:
const processedPayouts = await prisma.payout.findMany({ where: { invoiceId, partnerId: partner.id, - status: "processed", + status: { + in: ["processed", "sent"], + }, }, include: commonInclude, });apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.ts (2)
114-124: Check workflow trigger result and fail fast on errors.
triggerWorkflows()returnsnullwhenever Upstash rejects the request, but the code currently ignores the result and continues. This leaves payouts stuck in"processing"status indefinitely if the workflow trigger fails.Apply this diff to validate the result:
if (stripePayouts.length > 0) { - await triggerWorkflows( + const workflowResult = await triggerWorkflows( stripePayouts.map(({ partnerId }) => ({ workflowId: "create-stripe-transfer", body: { partnerId, invoiceId, chargeId, }, })), ); + + if (!workflowResult) { + throw new Error( + `Failed to trigger create-stripe-transfer workflows for invoice ${invoiceId}`, + ); + } }
137-142: Treat missing QStash messageId as a hard failure.If
publishJSONreturns without amessageId, we log the error but still return success, which drops remaining payouts because no follow-up job runs. This should bubble up as an error so the cron system can retry.Apply this diff:
if (!response.messageId) { await log({ message: `Error scheduling next batch of payouts for invoice ${invoiceId}: ${JSON.stringify(response)}`, type: "errors", }); - } - - return logAndRespond( - `Scheduled next batch of payouts for invoice ${invoiceId} with QStash message ${response.messageId}`, - ); + throw new Error( + `Failed to schedule next batch of payouts for invoice ${invoiceId}`, + ); + } + + return logAndRespond( + `Scheduled next batch of payouts for invoice ${invoiceId} with QStash message ${response.messageId}`, + );
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (2)
apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts(1 hunks)apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.ts(1 hunks)
π§° Additional context used
π§ Learnings (3)
π Common learnings
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-08-25T17:33:45.072Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts:12-12
Timestamp: 2025-08-25T17:33:45.072Z
Learning: The WorkflowTrigger enum in packages/prisma/schema/workflow.prisma contains three values: leadRecorded, saleRecorded, and commissionEarned. All three are properly used throughout the codebase.
Applied to files:
apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.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/cron/payouts/create-stripe-transfer/route.ts
𧬠Code graph analysis (2)
apps/web/app/(ee)/api/cron/payouts/send-stripe-payouts/route.ts (5)
apps/web/app/(ee)/api/cron/payouts/send-paypal-payouts/route.ts (1)
POST(21-118)apps/web/app/(ee)/api/cron/campaigns/broadcast/route.ts (1)
POST(37-317)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/cron/qstash-workflow.ts (1)
triggerWorkflows(39-87)apps/web/lib/api/errors.ts (1)
handleAndReturnErrorResponse(175-178)
apps/web/app/(ee)/api/cron/payouts/create-stripe-transfer/route.ts (5)
apps/web/lib/zod/schemas/payouts.ts (1)
createStripeTransferWorkflowSchema(93-97)packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/partners/create-stripe-transfer.ts (1)
createStripeTransfer(16-156)packages/email/src/index.ts (1)
sendBatchEmail(31-70)packages/email/src/templates/partner-payout-processed.tsx (1)
PartnerPayoutProcessed(17-119)
β° 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
Summary by CodeRabbit
New Features
Refactor
Behavioral change