-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Fix cron/withdrawal again fr #3021
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.
|
WalkthroughSplit 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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: 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 payoutsThe 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:93apps/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 callsImporting 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 driftHard‑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 balanceavailable[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 payoutsamountToKeepOnStripe 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
📒 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 logsThe currencyFormatter output improves operability and support triage.
Summary by CodeRabbit
Bug Fixes
Chores