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

    • Removed a temporary restriction that was blocking certain payout processing operations.
  • Chores

    • Optimized withdrawal processing query logic for improved efficiency and performance.

@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 2:11pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

Split withdrawal cron's balance aggregation from a single query into three separate queries (two processing invoices with distinct payment method filters and time thresholds, one payout query). Removed temporary hard-coded payout-skip logic from the charge-succeeded webhook and updated logging throughout to use currency-formatted values.

Changes

Cohort / File(s) Summary
Withdrawal balance calculation refactor
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts
Split single processing invoice aggregate into two separate queries filtering by payment method (ach/sepa/acss vs ach_fast) and time thresholds (THREE_DAYS_AGO, ONE_DAY_AGO). Updated balance calculations to combine amounts from both queries and payouts. Changed logging from raw numeric to currency-formatted output.
Webhook cleanup
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts
Removed log import and deleted hard-coded payout-skip condition for specific invoice ID.

Sequence Diagram(s)

sequenceDiagram
    participant Cron as Cron Job
    participant DB as Database
    participant Stripe as Stripe API

    Cron->>DB: Query processing invoices<br/>(direct debit: ach/sepa/acss)<br/>createdAt >= THREE_DAYS_AGO
    DB-->>Cron: processingInvoicesDirectDebit
    
    Cron->>DB: Query processing invoices<br/>(fast ACH: ach_fast)<br/>createdAt >= ONE_DAY_AGO
    DB-->>Cron: processingInvoicesDirectDebitFast
    
    Cron->>DB: Query completed payouts
    DB-->>Cron: processedPayouts
    
    Note over Cron: Calculate total reserved amount<br/>from both processing queries
    
    Cron->>Stripe: Fetch available balance
    Stripe-->>Cron: Balance data
    
    Note over Cron: Calculate amountToWithdraw<br/>= available - reserved
    
    alt Amount threshold met
        Cron->>Stripe: Create payout
        Stripe-->>Cron: Payout result
    else Skip withdrawal
        Note over Cron: Log skip with currency<br/>formatted amount
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Key areas requiring attention:
    • Verify the logic splitting processing invoices by payment method and ensuring time thresholds (THREE_DAYS_AGO vs ONE_DAY_AGO) are correctly applied to respective queries
    • Confirm that combining processingInvoicesDirectDebit and processingInvoicesDirectDebitFast amounts accurately replaces the original single aggregate
    • Review the balance calculation logic to ensure amountToKeepOnStripe uses the correct combined amounts
    • Check that currency formatting in logging doesn't mask any numeric precision issues

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐰 The payment paths now split in two,
Direct and speedy, each with their due,
Time thresholds guide the flow so true,
Currency logged for the human's view,
No skip tricks hide—just honest hue!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
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.
Title Check ❓ Inconclusive The title "Fix cron/withdrawal again fr" references the correct component being modified (the withdrawal cron route), which is the primary file changed in this PR. However, the title is vague and lacks specificity about what the fix actually accomplishes. The term "Fix" is generic, and the casual language "again fr" is non-descriptive and unprofessional. While a developer would recognize this relates to the withdrawal cron functionality, they would not understand the nature of the changes—specifically that the PR refactors query logic, introduces new time thresholds, splits processing invoices into categories, and updates formatting. The title does not adequately convey meaningful information about the changeset to someone scanning commit history. Consider using a more specific and professional title that describes the actual fix, such as "Refactor withdrawal cron to split processing invoice queries by payment method" or "Fix withdrawal cron invoice processing with separate direct debit and fast ACH queries". This would help reviewers and future maintainers understand the substantive changes without reading the full diff.
✅ 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-fr

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

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/cron/trigger-withdrawal/route.ts (1)

99-105: Add idempotency to payouts.create to prevent duplicate payouts

The Stripe API supports idempotency for safely retrying requests without accidentally performing the same operation twice. When creating or updating an object, use an idempotency key. Then, if a connection error occurs, you can safely repeat the request without risk of creating a second object. Cron retries or concurrent invocations can create duplicate payouts without an idempotency key—this is a legitimate risk across all three stripe.payouts.create() calls in the codebase.

However, the proposed idempotency key format (withdrawal:${date}:${amount}) has a flaw: multiple withdrawals of the same amount on the same day would reuse the same key, causing later requests to return the cached result of the first payout instead of creating a new one. Make sure that the key is unique, such as a universally unique identifier (UUID) or a combination of customer ID and order ID.

Use a stronger key that includes a unique identifier per withdrawal (e.g., database payout ID, or timestamp with millisecond precision):

-const createdPayout = await stripe.payouts.create({
-  amount: balanceToWithdraw,
-  currency: "usd",
-});
+const idempotencyKey = `withdrawal:${Date.now()}:${balanceToWithdraw}`;
+const createdPayout = await stripe.payouts.create(
+  { amount: balanceToWithdraw, currency: "usd" },
+  { idempotencyKey }
+);

Also update the other two stripe.payouts.create() calls at:

  • apps/web/app/(ee)/api/cron/payouts/balance-available/route.ts:93
  • apps/web/scripts/stripe/manual-payouts.ts:30
🧹 Nitpick comments (5)
apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts (1)

7-7: Guard against using dev domain in prod QStash calls

Importing APP_DOMAIN_WITH_NGROK is fine for local/dev, but ensure prod uses the canonical app domain and no double-slash issues when concatenating. Consider switching to an env‑gated base URL or a helper that normalizes slashes.

apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (4)

18-22: Prefer stable UTC date math (and consider business‑day intent)

Use date‑fns subDays/subBusinessDays with a single “now” to avoid DST/local‑tz drift and multiple now() calls.

+import { subDays /* or subBusinessDays */ } from "date-fns";
-const THREE_DAYS_AGO = new Date(new Date().setDate(new Date().getDate() - 3));
-const ONE_DAY_AGO = new Date(new Date().setDate(new Date().getDate() - 1));
+const now = new Date();
+const THREE_DAYS_AGO = subDays(now, 3); // or subBusinessDays(now, 3)
+const ONE_DAY_AGO = subDays(now, 1);    // or subBusinessDays(now, 1)

23-68: Centralize paymentMethod literals to avoid drift

Hard‑coded strings ("ach", "sepa", "acss", "ach_fast") are easy to mistype. Extract a typed constant/enum and reuse here and at write‑sites.

+// near top
+const DIRECT_DEBIT_METHODS = ["ach","sepa","acss"] as const;
 ...
- paymentMethod: {
-   in: ["ach", "sepa", "acss"],
- },
+ paymentMethod: { in: [...DIRECT_DEBIT_METHODS] },
 ...
- paymentMethod: "ach_fast",
+ paymentMethod: "ach_fast",

70-72: Select the correct currency from Stripe balance

available[0]/pending[0] assumes USD is first. If multiple currencies exist, this can be wrong. Find USD explicitly (or fail fast).

-const currentAvailableBalance = stripeBalanceData.available[0].amount;
-const currentPendingBalance = stripeBalanceData.pending[0].amount;
+const usdAvail = stripeBalanceData.available.find((b) => b.currency === "usd");
+const usdPend  = stripeBalanceData.pending.find((b) => b.currency === "usd");
+if (!usdAvail || !usdPend) {
+  return logAndRespond("USD balance buckets not found, skipping...");
+}
+const currentAvailableBalance = usdAvail.amount;
+const currentPendingBalance = usdPend.amount;

73-80: Verify reserve math doesn’t double‑count processed payouts

amountToKeepOnStripe adds processedPayoutsAmount. Stripe typically deducts created payouts from “available” immediately, so subtracting it again may under‑withdraw. If your “processed” state predates Stripe’s available deduction, this is fine—otherwise remove it or switch to Stripe’s balance transactions to derive reserve.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31d5d18 and 51e3656.

📒 Files selected for processing (2)
  • apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.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 (1)
apps/web/app/(ee)/api/cron/trigger-withdrawal/route.ts (1)

83-90: Nice: human‑readable currency logs

The currencyFormatter output improves operability and support triage.

@steven-tey steven-tey merged commit fa53bf4 into main Oct 28, 2025
9 checks passed
@steven-tey steven-tey deleted the fix-withdrawal-again-fr branch October 28, 2025 14:29
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.

2 participants