Add workflows/create-partner-commission#3961
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR moves partner commission creation to queued Upstash (QStash) workflows, adds a create-partner-commission workflow route that computes eligibility and creates commissions, replaces legacy trigger/logger helpers with triggerQStashWorkflow and getWorkflowConfig, introduces a Zod schema and exported type for commission queue requests, and updates many callsites to enqueue commission creation and consume queued results. ChangesAsync Partner Commission Workflow Refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/actions/partners/create-manual-commission.ts (1)
250-274:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInconsistent payload:
userobject instead ofuserId.In the
useExistingEventspath, the commission payload passes the fulluserobject (line 269), but other paths in this file useuserId: user.id. This inconsistency may cause validation or processing issues in the workflow.🐛 Proposed fix
commissionsToCreate.push( ...saleEventData.map((saleEvent) => ({ event: "sale" as const, programId, partnerId, linkId: link.id, customerId: customer.id, eventId: saleEvent.event_id, quantity: 1, amount: saleEvent.amount, currency: saleEvent.currency, invoiceId: saleEvent.invoice_id, createdAt: new Date(saleEvent.timestamp), // if the invoice payment was refunded on Stripe, set the commission status to refunded as well ...(stripeCustomerInvoices.find( (invoice) => invoice.id === saleEvent.invoice_id, )?.refunded && { status: "refunded", }), - user, + userId: user.id, context: { customer: { country: customer.country }, }, })), );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/actions/partners/create-manual-commission.ts` around lines 250 - 274, The saleEvent mapping that pushes to commissionsToCreate is passing the full user object (property user) instead of the expected userId; change the payload created in the saleEventData.map (the object inside commissionsToCreate.push) to remove the user property and add userId: user.id so it matches the other code paths and validation expectations (ensure the key is exactly userId and not user to keep consistency with the rest of the commission creation logic).
🧹 Nitpick comments (6)
apps/web/app/(ee)/api/workflows/create-partner-commission/route.ts (4)
315-338: ⚡ Quick winAvoid recalculating subscription duration.
Lines 326-329 recalculate
subscriptionDurationMonthsusing the same logic already performed at lines 223-228 and stored in thecontextobject (line 235). This duplication is unnecessary and may introduce inconsistencies if the calculations diverge.♻️ Proposed refactor
// Recurring sale reward (maxDuration > 0) else { - const subscriptionDurationMonths = differenceInMonths( - createdAt ?? new Date(), // account for custom commission creation date - firstCommission.createdAt, - ); + const subscriptionDurationMonths = context?.customer?.subscriptionDurationMonths ?? 0; if (subscriptionDurationMonths >= reward.maxDuration) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts around lines 315 - 338, The subscription duration is being recalculated in the block that checks reward.maxDuration; instead of recomputing differenceInMonths, use the previously computed value stored on the context (context.subscriptionDurationMonths) to avoid duplication and drift. Replace the local subscriptionDurationMonths calculation in the maxDuration branch with a read of context.subscriptionDurationMonths (and fallback to 0 or a safe default if undefined), leaving the existing logAndReturn calls, and keep references to reward, firstCommission, createdAt, partnerId, and event unchanged.
501-534: ⚡ Quick winConsider logging individual side-effect failures.
Promise.allSettled(lines 501-534) prevents individual failures from aborting the workflow, which is appropriate for side effects. However, failures are currently silent. Consider logging rejected promises to aid debugging.♻️ Proposed enhancement
- await Promise.allSettled([ + const results = await Promise.allSettled([ sendWorkspaceWebhook({ ... }), ... ]); + + results.forEach((result, index) => { + if (result.status === 'rejected') { + console.error(`Side effect ${index} failed:`, result.reason); + } + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts around lines 501 - 534, The Promise.allSettled call silences failures from sendWorkspaceWebhook, sendPartnerPostback, syncTotalCommissions, and notifyPartnerCommission; after awaiting Promise.allSettled([...]) inspect the returned results array and log any rejected entries with contextual info (include the function name, partnerId, programId, workspace id, and the rejection reason) so individual side-effect failures are visible; use the unique call sites (sendWorkspaceWebhook, sendPartnerPostback, syncTotalCommissions, notifyPartnerCommission) to map results to their source when creating the log message.
166-186: ⚡ Quick winRename
contextparameter to avoid shadowing.Line 183: The destructured
contextvariable shadows thecontextparameter from the Upstash workflow (line 69). While the workflowcontextis not used insidestepCreateCommission, this shadowing reduces code clarity.♻️ Proposed refactor
async function stepCreateCommission( input: StepFunctionInput, ): Promise<StepCreateCommissionOutput> { let { event, partnerId, programId, linkId, customerId, eventId, invoiceId, amount, quantity, currency, description, createdAt, userId, - context, + context: rewardContext, programEnrollment, } = input;Then update references to
context→rewardContextthroughout the function (lines 231, 233, 237, 249).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts around lines 166 - 186, The destructured variable named context inside the stepCreateCommission function is shadowing the Upstash workflow context parameter; rename the destructured context to rewardContext and update all uses inside stepCreateCommission (e.g., where context is accessed later in the function) to rewardContext so the workflow-level context is no longer shadowed and clarity is preserved; ensure you update every reference (places currently using context within stepCreateCommission such as the reward handling/creation code) to the new rewardContext identifier.
385-385: 💤 Low valueClarify the TODO comment about
createdAt.Line 385 includes a
TODO: Check thiscomment for the conditionalcreatedAtspread. If this is a known issue or requires verification, consider documenting what needs to be checked or opening a follow-up issue to track it.Do you want me to open a new issue to track this TODO, or can you clarify what needs to be checked here?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts at line 385, The TODO on the conditional spread "(...(createdAt && { createdAt }))" needs clarification: decide whether createdAt should be accepted from the request or always set server-side, then update the code and comment accordingly — if createdAt must be client-controlled, add explicit validation in the createPartnerCommission handler (validate format/allowed range) and keep the spread with a clear comment; if it must be server-assigned, remove the spread and set createdAt when constructing the model (e.g., in the createPartnerCommission function) and delete the TODO; if you can’t decide now, replace the TODO with a one-line TODO linked to a freshly opened issue tracking whether createdAt may be supplied by clients and add a brief test covering both behaviors.apps/web/lib/cron/qstash-workflow.ts (1)
79-111: ⚡ Quick winConsider adding type-safe accessors for correlation fields.
getWorkflowConfigaccessesbodyfields (e.g.,body.programId,body.partnerId) without type checking. Sincebodyis typed asRecord<string, unknown>, these accesses are not type-safe and could reference missing or mistyped fields.Consider defining input schemas for each workflow type and using them to validate/parse the body before extracting correlation data.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/cron/qstash-workflow.ts` around lines 79 - 111, getWorkflowConfig currently reads properties off body (typed Record<string, unknown>) unsafely; add type-safe accessors by defining per-workflow input schemas (e.g., zod schemas or TypeScript runtime validators) for each workflowType (like "partner-approved" and "create-partner-commission"), validate/parse body inside getWorkflowConfig (or a helper like parsePartnerApprovedBody / parseCreatePartnerCommissionBody) and then extract strongly-typed fields into the correlation object; ensure on validation failure you handle/throw a clear error or return an empty correlation consistently so downstream code sees only validated, typed values.apps/web/lib/partners/create-partner-commission.ts (1)
39-389: ⚡ Quick winRemove the large commented-out legacy block before merge.
Lines 39–389 are the old
createPartnerCommissionbody left as comments. This logic now lives in the workflow route, so keeping ~350 lines of dead code here hurts readability and risks drift. Git history preserves it if needed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/partners/create-partner-commission.ts` around lines 39 - 389, Remove the large commented-out legacy implementation block (the ~lines 39–389 containing the old createPartnerCommission body) from apps/web/lib/partners/create-partner-commission.ts so the file only contains the current active implementation; ensure you delete the entire commented region (the old earnings/reward/status logic, prisma lookups, commission.create try/catch and associated logs) while leaving any exports or the current createPartnerCommission function intact so git history retains the removed code if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/lib/zod/schemas/commissions.ts`:
- Around line 443-444: The Zod schema uses redundant and inconsistent modifiers:
remove the redundant .optional() from the amount field (change amount:
z.number().default(0).optional() to just amount: z.number().default(0)) and make
quantity explicitly optional to match intent (change quantity:
z.number().default(1) to quantity: z.number().default(1).optional()); this
leverages Zod 4 behavior where .default() already makes input optional while
keeping defaults applied.
---
Outside diff comments:
In `@apps/web/lib/actions/partners/create-manual-commission.ts`:
- Around line 250-274: The saleEvent mapping that pushes to commissionsToCreate
is passing the full user object (property user) instead of the expected userId;
change the payload created in the saleEventData.map (the object inside
commissionsToCreate.push) to remove the user property and add userId: user.id so
it matches the other code paths and validation expectations (ensure the key is
exactly userId and not user to keep consistency with the rest of the commission
creation logic).
---
Nitpick comments:
In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts:
- Around line 315-338: The subscription duration is being recalculated in the
block that checks reward.maxDuration; instead of recomputing differenceInMonths,
use the previously computed value stored on the context
(context.subscriptionDurationMonths) to avoid duplication and drift. Replace the
local subscriptionDurationMonths calculation in the maxDuration branch with a
read of context.subscriptionDurationMonths (and fallback to 0 or a safe default
if undefined), leaving the existing logAndReturn calls, and keep references to
reward, firstCommission, createdAt, partnerId, and event unchanged.
- Around line 501-534: The Promise.allSettled call silences failures from
sendWorkspaceWebhook, sendPartnerPostback, syncTotalCommissions, and
notifyPartnerCommission; after awaiting Promise.allSettled([...]) inspect the
returned results array and log any rejected entries with contextual info
(include the function name, partnerId, programId, workspace id, and the
rejection reason) so individual side-effect failures are visible; use the unique
call sites (sendWorkspaceWebhook, sendPartnerPostback, syncTotalCommissions,
notifyPartnerCommission) to map results to their source when creating the log
message.
- Around line 166-186: The destructured variable named context inside the
stepCreateCommission function is shadowing the Upstash workflow context
parameter; rename the destructured context to rewardContext and update all uses
inside stepCreateCommission (e.g., where context is accessed later in the
function) to rewardContext so the workflow-level context is no longer shadowed
and clarity is preserved; ensure you update every reference (places currently
using context within stepCreateCommission such as the reward handling/creation
code) to the new rewardContext identifier.
- Line 385: The TODO on the conditional spread "(...(createdAt && { createdAt
}))" needs clarification: decide whether createdAt should be accepted from the
request or always set server-side, then update the code and comment accordingly
— if createdAt must be client-controlled, add explicit validation in the
createPartnerCommission handler (validate format/allowed range) and keep the
spread with a clear comment; if it must be server-assigned, remove the spread
and set createdAt when constructing the model (e.g., in the
createPartnerCommission function) and delete the TODO; if you can’t decide now,
replace the TODO with a one-line TODO linked to a freshly opened issue tracking
whether createdAt may be supplied by clients and add a brief test covering both
behaviors.
In `@apps/web/lib/cron/qstash-workflow.ts`:
- Around line 79-111: getWorkflowConfig currently reads properties off body
(typed Record<string, unknown>) unsafely; add type-safe accessors by defining
per-workflow input schemas (e.g., zod schemas or TypeScript runtime validators)
for each workflowType (like "partner-approved" and "create-partner-commission"),
validate/parse body inside getWorkflowConfig (or a helper like
parsePartnerApprovedBody / parseCreatePartnerCommissionBody) and then extract
strongly-typed fields into the correlation object; ensure on validation failure
you handle/throw a clear error or return an empty correlation consistently so
downstream code sees only validated, typed values.
In `@apps/web/lib/partners/create-partner-commission.ts`:
- Around line 39-389: Remove the large commented-out legacy implementation block
(the ~lines 39–389 containing the old createPartnerCommission body) from
apps/web/lib/partners/create-partner-commission.ts so the file only contains the
current active implementation; ensure you delete the entire commented region
(the old earnings/reward/status logic, prisma lookups, commission.create
try/catch and associated logs) while leaving any exports or the current
createPartnerCommission function intact so git history retains the removed code
if needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b8ebff2b-2ad2-49d6-ba5e-e9796a647dc5
📒 Files selected for processing (23)
apps/web/app/(ee)/api/cron/utils.tsapps/web/app/(ee)/api/partner-profile/referrals/route.tsapps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.tsapps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.tsapps/web/app/(ee)/api/workflows/create-partner-commission/route.tsapps/web/app/(ee)/api/workflows/partner-approved/route.tsapps/web/lib/actions/partners/bulk-approve-partners.tsapps/web/lib/actions/partners/create-clawback.tsapps/web/lib/actions/partners/create-manual-commission.tsapps/web/lib/api/conversions/track-lead.tsapps/web/lib/api/conversions/track-sale.tsapps/web/lib/api/partners/applications/approve-partner.tsapps/web/lib/bounty/api/approve-bounty-submission.tsapps/web/lib/cron/index.tsapps/web/lib/cron/qstash-workflow-logger.tsapps/web/lib/cron/qstash-workflow.tsapps/web/lib/integrations/shopify/create-sale.tsapps/web/lib/integrations/shopify/process-order.tsapps/web/lib/partners/create-partner-commission.tsapps/web/lib/types.tsapps/web/lib/zod/schemas/commissions.tsapps/web/lib/zod/schemas/rewards.tsapps/web/scripts/programs/backfill-reuse-commission.ts
💤 Files with no reviewable changes (1)
- apps/web/lib/cron/qstash-workflow-logger.ts
…ommission after approval
There was a problem hiding this comment.
Actionable comments posted: 2
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/workflows/create-partner-commission/route.ts (1)
390-413:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
customeris never returned at top level — fraud detection and postback customer data silently break.The
selectnestscustomerinside the createdcommission, and the success path returnslogAndReturn({ commission, isFirstCommission, ... })without a top-levelcustomer. ButStepCreateCommissionOutputdeclarescustomeras a separate field, and the handler at Line 89 destructures it (const { commission, customer, isFirstCommission } = ...) and forwards it tostepRunSideEffects. Sincecustomeris optional in the output type, TypeScript doesn't catch this, so at runtimecustomeris alwaysundefined.Downstream effects:
detectAndRecordFraudEvent(Line 457) is gated onif (customer && eventId && clickEvent)→ never runs.- The partner postback payload (Line 543) is built with
customer: undefined.🐛 Proposed fix: split
customerout of the created commission- const commission = await prisma.commission.create({ + const { customer, ...commission } = await prisma.commission.create({ data: { ... }, select: { id: true, earnings: true, currency: true, customer: { select: { id: true, email: true, name: true, }, }, }, }); console.log(prettyPrint(commission)); const isFirstCommission = event !== "custom" ? firstCommission === null : undefined; return logAndReturn({ commission, + customer, isFirstCommission, outputLog: `Created a ${event} commission ${commission.id} (${currencyFormatter(commission.earnings, { currency: commission.currency })}) for ${partnerId}`, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts around lines 390 - 413, The created DB result currently nests customer inside commission so the handler never returns a top-level customer; after the create call (the variable commission), extract the nested customer into its own const (e.g. const customer = commission.customer) and build a new commission object without the customer property (or delete commission.customer) and then pass { commission: newCommission, customer, isFirstCommission, ... } into logAndReturn; this ensures StepCreateCommissionOutput actually includes a top-level customer consumed by the caller (and downstream functions like stepRunSideEffects/detectAndRecordFraudEvent receive a real customer object).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts:
- Line 404: Remove the leftover debug log call
console.log(prettyPrint(commission)) from the create-partner-commission handler
in route.ts; do not replace it with another dump of commission (which may
contain PII), and if you need structured telemetry use the existing sanitized
fields or processLogger with explicitly non-PII data instead.
In `@apps/web/lib/cron/qstash-workflow.ts`:
- Line 31: The QStash workflow trigger is using APP_DOMAIN which is not
reachable from Upstash in dev; update the URL construction in
apps/web/lib/cron/qstash-workflow.ts to use APP_DOMAIN_WITH_NGROK (or a clear
env override) instead of APP_DOMAIN so Upstash can reach local
endpoints—specifically change the url that builds
`${APP_DOMAIN}/api/workflows/${workflow.workflowType}` to use
APP_DOMAIN_WITH_NGROK (or read NEXT_PUBLIC_NGROK_URL when not in production) so
workflow.workflowType endpoints are publicly reachable during local testing.
---
Outside diff comments:
In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts:
- Around line 390-413: The created DB result currently nests customer inside
commission so the handler never returns a top-level customer; after the create
call (the variable commission), extract the nested customer into its own const
(e.g. const customer = commission.customer) and build a new commission object
without the customer property (or delete commission.customer) and then pass {
commission: newCommission, customer, isFirstCommission, ... } into logAndReturn;
this ensures StepCreateCommissionOutput actually includes a top-level customer
consumed by the caller (and downstream functions like
stepRunSideEffects/detectAndRecordFraudEvent receive a real customer object).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 26c84dc9-aa21-4703-a69f-f2776f054b95
📒 Files selected for processing (3)
apps/web/app/(ee)/api/workflows/create-partner-commission/route.tsapps/web/lib/bounty/api/approve-bounty-submission.tsapps/web/lib/cron/qstash-workflow.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/bounty/api/approve-bounty-submission.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/tests/utils/verify-commission.ts`:
- Line 14: The inline comment describing the test timeout is stale: update the
comment that currently reads “timeout after 45 seconds” to match the constant
TIMEOUT_MS (60 seconds) in this file (verify-commission.ts); locate the comment
near the verify/timeout assertion and change the text to “timeout after 60
seconds” (or reference TIMEOUT_MS) so the comment and the TIMEOUT_MS constant
stay consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c7d34cbc-636d-4087-8c66-37e6a3c838dd
📒 Files selected for processing (2)
apps/web/tests/utils/verify-commission.tsapps/web/tests/utils/verify-fraud-event.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/tests/utils/verify-fraud-event.ts
|
@coderabbitai full review, any breaking changes from existing commission creation behavior? |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Read the new workflow route
cat apps/web/app/\(ee\)/api/workflows/create-partner-commission/route.tsLength of output: 16830 🏁 Script executed: #!/bin/bash
# Read the queue helper
cat apps/web/lib/partners/queue-partner-commission-creation.tsLength of output: 1193 🏁 Script executed: #!/bin/bash
# Read the updated track-lead and track-sale
cat apps/web/lib/api/conversions/track-lead.ts
cat apps/web/lib/api/conversions/track-sale.tsLength of output: 28565 🏁 Script executed: #!/bin/bash
# Read the updated qstash-workflow.ts
cat apps/web/lib/cron/qstash-workflow.tsLength of output: 3020 🏁 Script executed: #!/bin/bash
# Read the updated approve-bounty-submission
cat apps/web/lib/bounty/api/approve-bounty-submission.tsLength of output: 5106 🏁 Script executed: #!/bin/bash
# Read the updated create-clawback and create-manual-commission
cat apps/web/lib/actions/partners/create-clawback.ts
cat apps/web/lib/actions/partners/create-manual-commission.tsLength of output: 17589 🏁 Script executed: #!/bin/bash
# Read the createPartnerCommissionSchema
cat apps/web/lib/zod/schemas/commissions.tsLength of output: 14688 🏁 Script executed: #!/bin/bash
# Check the checkout-session-completed and invoice-paid webhooks
cat apps/web/app/\(ee\)/api/stripe/integration/webhook/checkout-session-completed.ts
cat apps/web/app/\(ee\)/api/stripe/integration/webhook/invoice-paid.tsLength of output: 33432 🏁 Script executed: #!/bin/bash
# Check the Shopify files
cat apps/web/lib/integrations/shopify/create-sale.ts
cat apps/web/lib/integrations/shopify/process-order.tsLength of output: 12323 🏁 Script executed: #!/bin/bash
# Check the cron index and utils
cat apps/web/lib/cron/index.ts
cat apps/web/app/\(ee\)/api/cron/utils.tsLength of output: 809
SummaryThe core architectural change is converting commission creation from synchronous and in-process ( 🔴 Breaking Changes / Bugs1.
|
| # | File | Issue |
|---|---|---|
| 1 | create-manual-commission.ts |
Add status to createPartnerCommissionSchema OR handle refunded post-creation; replace user with userId: user.id in useExistingEvents path |
| 2 | queue-partner-commission-creation.ts |
Pass commission earnings to constructWebhookPartner — OR document that sale.created/lead.created webhooks intentionally omit the new commission from totalCommissions |
| 3 | process-order.ts, checkout-session-completed.ts |
Pass clickEvent in promo-code/discount-code lead commission payloads to restore fraud detection |
| 4 | Workflow route | Add idempotency guard for WorkflowRetryAfterError to prevent duplicate commissions for events without unique constraints |
✅ Actions performed
Full review triggered.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)
698-711:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPass
clickEventto restore fraud detection for promo-code leads.The
clickEventis available (line 646) but not passed here. The workflow's fraud detection requiresclickEvent.urlandclickEvent.refererto function. Without it, fraud checks are silently skipped for promo-code attributions—a regression from the prior synchronous behavior.🐛 Proposed fix
result = await queuePartnerCommissionCreation({ event: "lead", programId: link.programId, partnerId: link.partnerId, linkId: link.id, eventId: leadEvent.event_id, customerId: customer.id, quantity: 1, context: { customer: { country: customer.country, }, }, + clickEvent: { + url: leadEvent.url, + referer: leadEvent.referer, + }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/stripe/integration/webhook/checkout-session-completed.ts around lines 698 - 711, The call to queuePartnerCommissionCreation is missing the clickEvent payload so promo-code lead fraud checks (which need clickEvent.url and clickEvent.referer) are skipped; update the invocation of queuePartnerCommissionCreation (the function call shown in the diff) to include the existing clickEvent object in the payload—e.g. add clickEvent into the context (alongside customer) or as the appropriate parameter expected by queuePartnerCommissionCreation so the workflow receives clickEvent.url and clickEvent.referer for fraud detection.apps/web/lib/integrations/shopify/process-order.ts (1)
157-170:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMissing
clickEventfor fraud detection in discount-code attribution.The
attributeViaDiscountCodefunction has access toclickEventfromrecordFakeClick()(line 92) but doesn't pass it toqueuePartnerCommissionCreation. This means fraud detection will be skipped for Shopify discount-code leads, unlike the previous synchronous behavior.To restore fraud detection, pass the
clickEvent:🐛 Proposed fix to pass clickEvent
result = await queuePartnerCommissionCreation({ event: "lead", programId: link.programId, partnerId: link.partnerId, linkId: link.id, eventId: leadEvent.event_id, customerId: customer.id, quantity: 1, context: { customer: { country: customer.country, }, }, + clickEvent: { + url: clickEvent.url, + referer: clickEvent.referer, + }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/integrations/shopify/process-order.ts` around lines 157 - 170, In attributeViaDiscountCode, include the clickEvent captured from recordFakeClick() when calling queuePartnerCommissionCreation so fraud detection runs; locate the attributeViaDiscountCode function and the queuePartnerCommissionCreation call and add the clickEvent (e.g., pass clickEvent: clickEvent or include it inside the context object alongside customer) so the queued partner commission creation receives the clickEvent for fraud checks.apps/web/lib/actions/partners/create-manual-commission.ts (1)
264-274:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winSchema mismatch:
statusanduserfields will be dropped or rejected.The commission payload includes
status: "refunded"(line 267) and the fulluserobject (line 269), butcreatePartnerCommissionSchema(incommissions.ts) does not accept astatusfield and expectsuserId: stringinstead ofuser: object.Impact:
- Refunded invoices will be queued as
pendingcommissions instead ofrefunded.- The
userobject will fail schema validation or be silently dropped, leavinguserIdundefined.🔧 Proposed fix
// if the invoice payment was refunded on Stripe, set the commission status to refunded as well ...(stripeCustomerInvoices.find( (invoice) => invoice.id === saleEvent.invoice_id, - )?.refunded && { - status: "refunded", - }), - user, + )?.refunded && {}), // TODO: handle refunded status in workflow or post-creation step + userId: user.id, context: { customer: { country: customer.country }, },Note: Since the schema doesn't support
status, you'll need to either:
- Add
statustocreatePartnerCommissionSchemaand handle it in the workflow, or- Mark refunded invoices in a post-creation step (e.g., via a separate workflow or commission update).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/actions/partners/create-manual-commission.ts` around lines 264 - 274, The commission payload currently includes status and a full user object which doesn't match createPartnerCommissionSchema (it expects userId: string and has no status); update the payload in create-manual-commission.ts (the mapping that builds the commission objects for createPartnerCommissionSchema) to replace user with userId (e.g., user.id) and remove the status field, and if refunded state must be recorded either add status to createPartnerCommissionSchema and propagate it through the workflow or implement a separate post-creation step to mark commissions as refunded.apps/web/lib/api/conversions/track-sale.ts (1)
467-499:⚠️ Potential issue | 🟠 Major | ⚡ Quick winWebhook partner data excludes the newly queued commission.
queuePartnerCommissionCreationreturnswebhookPartnerbuilt from the enrollment fetched before the commission is queued (line 472). Thesale.createdwebhook (line 539) will include partner totals that don't reflect this new commission.Impact:
partner.totalCommissionsand related aggregates in the webhook payload will be lower than expected.- This is a behavioral change from the previous synchronous
createPartnerCommissionflow, which included the new commission in totals.💡 Suggested fix
Option 1 (preferred): Pass the earnings/amount to
queuePartnerCommissionCreationand have it compute updated totals before returningwebhookPartner.Option 2: Fetch or recompute partner totals after queuing the commission (before sending the webhook).
Option 3: Document that webhook totals may lag and rely on eventual consistency (the workflow will update totals later).
Also applies to: 539-539
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/api/conversions/track-sale.ts` around lines 467 - 499, The webhook payload currently uses webhookPartner returned by queuePartnerCommissionCreation which is based on enrollment data fetched before queuing the commission, causing partner totals to omit the new commission; update queuePartnerCommissionCreation (or its call site in track-sale.ts) to include the commission amount/earnings (e.g., pass saleData.amount or computed earnings) and have queuePartnerCommissionCreation compute and return updated totals in webhookPartner before resolving, or alternatively, after awaiting queuePartnerCommissionCreation, re-fetch/recompute partner totals and replace webhookPartner fields used by the sale.created webhook so the payload includes the newly queued commission.
♻️ Duplicate comments (1)
apps/web/tests/utils/verify-commission.ts (1)
46-46:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winStale comment: timeout is now 60s, not 45s.
TIMEOUT_MSis60000, but Line 46 still reads "timeout after 45 seconds."Suggested patch
- // Poll for commission every 5 seconds, timeout after 45 seconds + // Poll for commission every 5 seconds, timeout after 60 seconds🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/tests/utils/verify-commission.ts` at line 46, Update the stale inline comment that says "timeout after 45 seconds" to reflect the actual TIMEOUT_MS value of 60000 (60 seconds); locate the TIMEOUT_MS constant and the polling comment near it in verify-commission.ts (the comment on or around the polling/pollEvery 5000ms logic) and change the text to "timeout after 60 seconds" so the comment matches the code behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/app/`(ee)/api/workflows/create-partner-commission/route.ts:
- Around line 388-407: The retry path can create duplicate click commissions
because stepCreateCommission calls prisma.commission.create without an
idempotency key and click rows lack a unique constraint; add a stable
idempotency key for clicks (e.g., clickEventId or workflowRunId) to the
Commission model (making it unique for click-type commissions) and change
stepCreateCommission to perform an idempotent write (use
prisma.commission.upsert or insert-on-conflict logic keyed on that new field
instead of prisma.commission.create), keeping existing eventId/invoiceId
behavior, and ensure error handling around WorkflowRetryAfterError/logAndReturn
still inspects the upsert result rather than blindly retrying.
- Around line 513-521: The code passes a possibly undefined group into
notifyPartnerCommission (via programEnrollment.partnerGroup ??
program.groups[0]) which will crash when notifyPartnerCommission reads
group.holdingPeriodDays; before calling notifyPartnerCommission, compute a local
group variable (e.g., const partnerGroup = programEnrollment.partnerGroup ??
program.groups[0]) and guard it: if partnerGroup is undefined then either skip
calling notifyPartnerCommission (and optionally log a warning) or throw a clear
error; otherwise pass partnerGroup into notifyPartnerCommission. Ensure you
update the call site that currently uses the inline fallback so
notifyPartnerCommission always receives a defined group.
In `@apps/web/tests/utils/verify-fraud-event.ts`:
- Line 32: Update the stale inline comment to reflect the actual timeout used:
change "timeout after 45 seconds" to "timeout after 60 seconds" (the constant
TIMEOUT_MS = 60000 governs the timeout) in the verify-fraud-event utility so the
comment matches the code.
---
Outside diff comments:
In
`@apps/web/app/`(ee)/api/stripe/integration/webhook/checkout-session-completed.ts:
- Around line 698-711: The call to queuePartnerCommissionCreation is missing the
clickEvent payload so promo-code lead fraud checks (which need clickEvent.url
and clickEvent.referer) are skipped; update the invocation of
queuePartnerCommissionCreation (the function call shown in the diff) to include
the existing clickEvent object in the payload—e.g. add clickEvent into the
context (alongside customer) or as the appropriate parameter expected by
queuePartnerCommissionCreation so the workflow receives clickEvent.url and
clickEvent.referer for fraud detection.
In `@apps/web/lib/actions/partners/create-manual-commission.ts`:
- Around line 264-274: The commission payload currently includes status and a
full user object which doesn't match createPartnerCommissionSchema (it expects
userId: string and has no status); update the payload in
create-manual-commission.ts (the mapping that builds the commission objects for
createPartnerCommissionSchema) to replace user with userId (e.g., user.id) and
remove the status field, and if refunded state must be recorded either add
status to createPartnerCommissionSchema and propagate it through the workflow or
implement a separate post-creation step to mark commissions as refunded.
In `@apps/web/lib/api/conversions/track-sale.ts`:
- Around line 467-499: The webhook payload currently uses webhookPartner
returned by queuePartnerCommissionCreation which is based on enrollment data
fetched before queuing the commission, causing partner totals to omit the new
commission; update queuePartnerCommissionCreation (or its call site in
track-sale.ts) to include the commission amount/earnings (e.g., pass
saleData.amount or computed earnings) and have queuePartnerCommissionCreation
compute and return updated totals in webhookPartner before resolving, or
alternatively, after awaiting queuePartnerCommissionCreation, re-fetch/recompute
partner totals and replace webhookPartner fields used by the sale.created
webhook so the payload includes the newly queued commission.
In `@apps/web/lib/integrations/shopify/process-order.ts`:
- Around line 157-170: In attributeViaDiscountCode, include the clickEvent
captured from recordFakeClick() when calling queuePartnerCommissionCreation so
fraud detection runs; locate the attributeViaDiscountCode function and the
queuePartnerCommissionCreation call and add the clickEvent (e.g., pass
clickEvent: clickEvent or include it inside the context object alongside
customer) so the queued partner commission creation receives the clickEvent for
fraud checks.
---
Duplicate comments:
In `@apps/web/tests/utils/verify-commission.ts`:
- Line 46: Update the stale inline comment that says "timeout after 45 seconds"
to reflect the actual TIMEOUT_MS value of 60000 (60 seconds); locate the
TIMEOUT_MS constant and the polling comment near it in verify-commission.ts (the
comment on or around the polling/pollEvery 5000ms logic) and change the text to
"timeout after 60 seconds" so the comment matches the code behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a76c9559-0037-48c4-a419-c389dde946f2
📒 Files selected for processing (27)
apps/web/app/(ee)/api/cron/utils.tsapps/web/app/(ee)/api/partner-profile/referrals/route.tsapps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.tsapps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.tsapps/web/app/(ee)/api/workflows/create-partner-commission/route.tsapps/web/app/(ee)/api/workflows/partner-approved/route.tsapps/web/lib/actions/partners/bulk-approve-partners.tsapps/web/lib/actions/partners/create-clawback.tsapps/web/lib/actions/partners/create-manual-commission.tsapps/web/lib/api/conversions/track-lead.tsapps/web/lib/api/conversions/track-sale.tsapps/web/lib/api/partners/applications/approve-partner.tsapps/web/lib/bounty/api/approve-bounty-submission.tsapps/web/lib/cron/index.tsapps/web/lib/cron/qstash-workflow-logger.tsapps/web/lib/cron/qstash-workflow.tsapps/web/lib/integrations/shopify/create-sale.tsapps/web/lib/integrations/shopify/process-order.tsapps/web/lib/partners/create-partner-commission.tsapps/web/lib/partners/queue-partner-commission-creation.tsapps/web/lib/types.tsapps/web/lib/zod/schemas/commissions.tsapps/web/lib/zod/schemas/rewards.tsapps/web/scripts/programs/backfill-reuse-commission.tsapps/web/tests/fraud/index.test.tsapps/web/tests/utils/verify-commission.tsapps/web/tests/utils/verify-fraud-event.ts
💤 Files with no reviewable changes (2)
- apps/web/lib/cron/qstash-workflow-logger.ts
- apps/web/lib/partners/create-partner-commission.ts
Summary by CodeRabbit
New Features
Refactor
Tests