-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Payout updates #2635
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
Payout updates #2635
Conversation
…StatusBadges to include 'sent' status
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
""" WalkthroughThis update introduces two new payout statuses, "processed" and "sent," and refactors payout processing logic for both Stripe and PayPal. It internalizes payout fetching within processing functions, adds a new Stripe webhook handler, restructures payout-related UI components, and updates email templates and URLs. Database schema and badge definitions are also expanded to support the new statuses. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WebApp
participant DB
participant Stripe
participant PayPal
participant Email
User->>WebApp: Initiate payout (triggered by event)
WebApp->>DB: Fetch invoice and relevant data
WebApp->>Stripe: (If Stripe) Create transfer/payout
Stripe-->>WebApp: Transfer/payout confirmation
WebApp->>DB: Update payouts to "sent" or "processed"
WebApp->>Email: Send notification (if applicable)
Stripe-->>WebApp: Send "payout.paid" webhook
WebApp->>DB: Update payouts to "completed"
WebApp->>Email: Send withdrawal confirmation (new template)
WebApp->>PayPal: (If PayPal) Create batch payout
PayPal-->>WebApp: Batch payout confirmation
WebApp->>DB: Update payouts as needed
Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (2)📓 Common learningsapps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (3)🧬 Code Graph Analysis (1)apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (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)
🔇 Additional comments (2)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
… payouts with transfers and modify payoutPaid to mark payouts as completed. Add stripePayoutId field to Payout model in Prisma schema.
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/charge-succeeded/send-stripe-payouts.ts (3)
112-114: Critical: Zero balance scenario still lacks proper handling.The zero balance condition continues without updating payout statuses, leaving them in an inconsistent state. This was previously flagged but remains unaddressed.
Apply this fix to handle the zero balance scenario properly:
if (updatedBalance <= 0) { + await prisma.payout.updateMany({ + where: { + id: { + in: payoutIds, + }, + }, + data: { + status: "processed", + }, + }); + + console.log( + `Skipping payouts for partner ${partner.id} - insufficient balance after withdrawal fee (${currencyFormatter(updatedBalance / 100)})`, + ); + continue; }
117-128: Critical: Stripe transfer creation lacks error handling.The Stripe API call can fail and leave payouts in an inconsistent state. This was previously flagged but remains unaddressed.
Wrap the transfer creation in proper error handling:
-const transfer = await stripe.transfers.create( - { - amount: updatedBalance, - currency: "usd", - transfer_group: invoiceId, - destination: partner.stripeConnectId!, - description: `Dub Partners payout for ${payouts.map((p) => p.id).join(", ")}`, - }, - { - idempotencyKey: `${invoiceId}-${partner.id}`, - }, -); +let transfer; +try { + transfer = await stripe.transfers.create( + { + amount: updatedBalance, + currency: "usd", + transfer_group: invoiceId, + destination: partner.stripeConnectId!, + description: `Dub Partners payout for ${payouts.map((p) => p.id).join(", ")}`, + }, + { + idempotencyKey: `${invoiceId}-${partner.id}`, + }, + ); +} catch (error) { + console.error( + `Failed to create Stripe transfer for partner ${partner.id}:`, + error, + ); + continue; +}
137-174: Consider adding transaction safety for database operations.While
Promise.allSettledhandles individual promise failures, wrapping the payout and commission updates in a database transaction would ensure atomicity and prevent partial updates.+await prisma.$transaction(async (tx) => { await Promise.allSettled([ - prisma.payout.updateMany({ + tx.payout.updateMany({ where: { id: { in: payoutIds, }, }, data: { stripeTransferId: transfer.id, status: "sent", paidAt: new Date(), }, }), - prisma.commission.updateMany({ + tx.commission.updateMany({ where: { payoutId: { in: payoutIds, }, }, data: { status: "paid", }, }), + ]); +}); +await Promise.allSettled([ partner.email ? sendEmail({ variant: "notifications", subject: "You've been paid!", email: partner.email, react: PartnerPayoutProcessed({ email: partner.email, program: latestInvoicePayout.program, payout: latestInvoicePayout, }), }) : Promise.resolve(), - ]); +]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/route.ts(2 hunks)apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts(1 hunks)apps/web/app/(ee)/api/paypal/webhook/payouts-item-succeeded.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx(1 hunks)packages/email/src/templates/partner-payout-processed.tsx(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/app/(ee)/api/cron/payouts/charge-succeeded/route.ts
- apps/web/app/(ee)/api/paypal/webhook/payouts-item-succeeded.ts
- apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.654Z
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
PR: dubinc/dub#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.
packages/email/src/templates/partner-payout-processed.tsx (3)
Learnt from: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.654Z
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: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts (2)
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.654Z
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
PR: dubinc/dub#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.
⏰ 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)
packages/email/src/templates/partner-payout-processed.tsx (5)
17-17: Function name correctly reflects the new payout lifecycle.The rename from
PartnerPayoutSenttoPartnerPayoutProcessedaligns with the new payout status workflow introduced in this PR, where "processed" represents an intermediate state before "sent".
73-73: Logo standardization improves consistency.Using the fixed
DUB_WORDMARKinstead of the dynamic program logo ensures consistent branding across email templates and eliminates potential issues with missing or invalid program logos.
95-100: Enhanced messaging provides clearer user guidance.The updated text effectively explains the Stripe Express account behavior and minimum withdrawal balance, providing users with actionable information about when transfers will occur and how to adjust settings.
105-105: URL change aligns with UI restructuring.The link to the general payouts overview page (
/payouts) is consistent with the URL standardization mentioned in the PR objectives and reflects the restructured payout components.
107-107: Link text matches the destination page.The change from "View payout" to "View payouts" correctly reflects that users are now directed to the general payouts overview rather than a specific payout detail page.
apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts (3)
22-54: LGTM: Well-structured payout fetching logic.The internal payout fetching improves encapsulation and ensures fresh data during processing. The filtering criteria correctly target payouts that need Stripe processing.
63-74: LGTM: Efficient payout grouping by partner.The grouping logic correctly consolidates payouts by partner ID, which will reduce API calls and transaction fees as intended by the refactoring.
83-102: LGTM: Proper handling of minimum withdrawal amounts.The logic correctly updates payouts to "processed" status when below the minimum threshold, maintaining state consistency without creating unnecessary transfers.
| } | ||
|
|
||
| const { invoiceId, chargeId, achCreditTransfer } = payload; | ||
| const latestInvoicePayout = payouts.find((p) => p.invoiceId === invoiceId)!; |
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.
🛠️ Refactor suggestion
Potential issue: Unsafe non-null assertion.
The non-null assertion ! assumes an invoice payout exists, but this could fail if the invoice contains no payouts.
Add proper validation:
-const latestInvoicePayout = payouts.find((p) => p.invoiceId === invoiceId)!;
+const latestInvoicePayout = payouts.find((p) => p.invoiceId === invoiceId);
+if (!latestInvoicePayout) {
+ console.log(`No payout found for invoice ${invoiceId}, skipping...`);
+ return;
+}📝 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.
| const latestInvoicePayout = payouts.find((p) => p.invoiceId === invoiceId)!; | |
| const latestInvoicePayout = payouts.find((p) => p.invoiceId === invoiceId); | |
| if (!latestInvoicePayout) { | |
| console.log(`No payout found for invoice ${invoiceId}, skipping...`); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts at
line 61, the code uses a non-null assertion operator (!) on the result of
payouts.find, which can cause runtime errors if no matching payout is found.
Replace the non-null assertion with a check to verify that latestInvoicePayout
is not undefined before using it, and handle the case where no payout is found
appropriately, such as returning early or throwing a descriptive error.
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: 0
🧹 Nitpick comments (3)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-sheet.tsx (3)
147-148: TypeScript ignore comment is acceptable but document the reasoning.The
@ts-ignorefortrailingZeroDisplayis reasonable since it's a newer API not yet reflected in TypeScript definitions. Consider adding a brief comment explaining this is a legitimate newer option.- // @ts-ignore – trailingZeroDisplay is a valid option but TS is outdated + // @ts-ignore – trailingZeroDisplay is a valid Intl.NumberFormat option but TS definitions are outdated trailingZeroDisplay: "stripIfInteger",
205-205: Consider removing autoFocus for better accessibility.Auto-focusing the business name input when the sheet opens can be jarring for users, especially those using screen readers or keyboard navigation. Consider letting users naturally tab to the first field they want to interact with.
- autoFocus className="block w-full rounded-md border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
268-284: Simplify the hook implementation.The current pattern with
useCallbackwrapping the component and thenuseMemofor the return value is overly complex. This can be simplified while maintaining the same functionality.export function usePartnerPayoutSettingsSheet() { const [showPartnerPayoutSettingsSheet, setShowPartnerPayoutSettingsSheet] = useState(false); - const PartnerPayoutSettingsSheetCallback = useCallback(() => { - return ( - <PartnerPayoutSettingsSheet - showPartnerPayoutSettingsSheet={showPartnerPayoutSettingsSheet} - setShowPartnerPayoutSettingsSheet={setShowPartnerPayoutSettingsSheet} - /> - ); - }, [showPartnerPayoutSettingsSheet, setShowPartnerPayoutSettingsSheet]); - - return useMemo( - () => ({ + const PartnerPayoutSettingsSheetComponent = () => ( + <PartnerPayoutSettingsSheet + showPartnerPayoutSettingsSheet={showPartnerPayoutSettingsSheet} + setShowPartnerPayoutSettingsSheet={setShowPartnerPayoutSettingsSheet} + /> + ); + + return { setShowPartnerPayoutSettingsSheet, - PartnerPayoutSettingsSheet: PartnerPayoutSettingsSheetCallback, - }), - [setShowPartnerPayoutSettingsSheet, PartnerPayoutSettingsSheetCallback], -); + PartnerPayoutSettingsSheet: PartnerPayoutSettingsSheetComponent, + }; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-sheet.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-button.tsx
- apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/payout-stats.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.654Z
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
PR: dubinc/dub#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.
⏰ 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 (5)
apps/web/app/(ee)/partners.dub.co/(dashboard)/payouts/partner-payout-settings-sheet.tsx (5)
162-172: Slider snap-to-value logic is well implemented.The closest value calculation correctly snaps the slider to predefined withdrawal amounts. The logic uses proper math and maintains form state correctly with
shouldDirty: true.
175-183: Dynamic hint display enhances user understanding.The conditional rendering based on withdrawal amount clearly communicates fee structure to users. The visual distinction between fee-based and free payouts with the PartyPopper icon is effective UX.
237-240: Scroll gradient effect implementation is clean.The scroll progress-based gradient provides good visual feedback. The opacity calculation using
Math.pow(scrollProgress, 2)creates a smooth fade effect that enhances the scrolling experience.
41-53: Component architecture follows good separation of concerns.The separation between the outer Sheet wrapper and inner form component is clean. This pattern allows for proper state management while keeping the Sheet API encapsulated.
75-91: Form submission and error handling are properly implemented.The async action handling with success/error callbacks, toast notifications, and cache invalidation using
mutatePrefixfollows established patterns in the codebase. The form state management withisDirtyfor the save button is appropriate.
|
@CodeRabbit final review |
|
Tip For best results, initiate chat on the files or code changes. @steven-tey This is an exemplary refactoring that significantly improves the payout system across multiple dimensions: ✅ Key StrengthsArchitecture Excellence:
Performance & Efficiency:
User Experience:
Code Quality:
🎯 Final AssessmentThis refactoring demonstrates excellent software engineering practices:
✅ APPROVED - No blockers identified. This is production-ready and represents a significant improvement to the payout system. The changes are well-structured, thoroughly implemented, and deliver meaningful value to both the platform and its users. 🧠 Learnings used |
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: 0
♻️ Duplicate comments (1)
apps/web/app/(ee)/api/stripe/connect/webhook/balance-available.ts (1)
99-119: Consider handling pagination for transfers.The code fetches only up to 100 transfers. If a partner has more than 100 transfers, some payouts might not get their
stripePayoutIdupdated.Implement pagination to handle all transfers:
-const transfers = await stripe.transfers.list({ - destination: stripeAccount, - limit: 100, -}); +const allTransfers = []; +let hasMore = true; +let startingAfter = undefined; + +while (hasMore) { + const transfers = await stripe.transfers.list({ + destination: stripeAccount, + limit: 100, + starting_after: startingAfter, + }); + + allTransfers.push(...transfers.data); + hasMore = transfers.has_more; + if (hasMore && transfers.data.length > 0) { + startingAfter = transfers.data[transfers.data.length - 1].id; + } +} + await prisma.payout.updateMany({ where: { status: "sent", stripePayoutId: null, stripeTransferId: { - in: transfers.data.map(({ id }) => id), + in: allTransfers.map(({ id }) => id), }, }, data: { stripePayoutId: payout.id, }, });
🧹 Nitpick comments (1)
apps/web/app/(ee)/api/stripe/connect/webhook/balance-available.ts (1)
12-14: Minor: Improve error message clarity.The error message refers to
stripeAccountin the message but it could be null, which might be confusing in logs.- console.error( - `Stripe connect account ${stripeAccount} not found. Skipping...`, - ); + console.error( + "Stripe connect account not found in webhook event. Skipping...", + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(ee)/api/paypal/webhook/payouts-item-succeeded.ts(0 hunks)apps/web/app/(ee)/api/stripe/connect/webhook/balance-available.ts(4 hunks)apps/web/ui/layout/sidebar/payout-stats.tsx(2 hunks)packages/email/src/templates/partner-payout-processed.tsx(5 hunks)
💤 Files with no reviewable changes (1)
- apps/web/app/(ee)/api/paypal/webhook/payouts-item-succeeded.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/ui/layout/sidebar/payout-stats.tsx
- packages/email/src/templates/partner-payout-processed.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.654Z
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
PR: dubinc/dub#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.
apps/web/app/(ee)/api/stripe/connect/webhook/balance-available.ts (3)
Learnt from: devkiran
PR: dubinc/dub#2635
File: packages/prisma/schema/payout.prisma:24-25
Timestamp: 2025-07-11T16:28:55.654Z
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: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.
Learnt from: steven-tey
PR: dubinc/dub#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.
⏰ 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 (4)
apps/web/app/(ee)/api/stripe/connect/webhook/balance-available.ts (4)
2-3: LGTM! New email notification capability added.The import of the new
PartnerPayoutWithdrawalInitiatedemail template aligns with the enhanced notification system for payout processing.
56-68: LGTM! Simplified balance calculation logic.The removal of complex currency conversion and withdrawal fee calculations makes the logic much cleaner while still properly accounting for pending/in-transit payouts.
77-81: Good handling of currency-specific requirements.The special handling for HUF and TWD currencies that require amounts divisible by 100 is correctly implemented and necessary for Stripe compliance.
121-131: LGTM! Well-implemented email notification system.The email notification properly uses the new template and includes the necessary payout details. The conditional check for partner email existence is good defensive programming.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Style
Chores