Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@devkiran
Copy link
Collaborator

@devkiran devkiran commented Dec 17, 2025

Summary by CodeRabbit

  • New Features
    • Partner commission now includes program enrollment status and that status is propagated to downstream workflows and webhooks.
  • Bug Fixes
    • Fraud detection now validates partner program enrollment (skips inactive enrollments) to reduce false positives.
    • Consolidated partner data sourcing across lead/sale/invoice flows to improve consistency and webhook payloads.
  • Chores
    • Updated schemas to carry program enrollment status in fraud contexts.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Dec 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
dub Ready Ready Preview Dec 17, 2025 10:16am

@devkiran devkiran requested a review from steven-tey December 17, 2025 10:00
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Walkthrough

Refactors commission creation so callers consume a unified createdCommission (including webhookPartner and new programEnrollment) and updates fraud detection to skip processing for inactive enrollments. Call sites and schemas were updated to use the new return shape.

Changes

Cohort / File(s) Change Summary
Stripe webhook handlers
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
Replace local webhookPartner with createdCommission result; derive webhookPartner and programEnrollment from it; update fraud/workflow/webhook payloads to use createdCommission?.webhookPartner and include programEnrollment.
Webhook utilities
apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts
Consolidate partner/commission into createdCommission; move side-effect dispatches into commission block; use createdCommission?.webhookPartner in final workspace webhook payload.
Conversion tracking
apps/web/lib/api/conversions/track-lead.ts, apps/web/lib/api/conversions/track-sale.ts
Store commission result in createdCommission; derive webhookPartner and programEnrollment from it; pass programEnrollment into fraud detection; remove WebhookPartner import.
Shopify integration
apps/web/lib/integrations/shopify/create-sale.ts
Promote createdCommission to outer scope; destructure webhookPartner and programEnrollment from it; update fraud context and webhook payloads to use commission-derived partner and enrollment.
Partner commission core
apps/web/lib/partners/create-partner-commission.ts
createPartnerCommission now returns { commission, webhookPartner, programEnrollment } across all paths (success, early returns, error).
Fraud detection
apps/web/lib/api/fraud/detect-record-fraud-event.ts
Import INACTIVE_ENROLLMENT_STATUSES; add early guard to log-and-skip processing when programEnrollment.status is inactive.
Schemas
apps/web/lib/zod/schemas/schemas.ts
Add programEnrollment (status only) to fraudEventContext; remove commission field from fraudEventContext.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify all callers correctly handle the new createdCommission return shape and that programEnrollment is present in all return branches.
  • Confirm fraud skip logic aligns with business expectations and INACTIVE_ENROLLMENT_STATUSES values.
  • Check webhook and workflow payload shapes where commission was removed and replaced by programEnrollment and webhookPartner.

Possibly related PRs

Suggested reviewers

  • steven-tey

Poem

🐇 I hopped through commission code at dawn,
Pulled webhookPartner from the commission brawn,
ProgramEnrollment, tidy and spry,
Keeps fraud checks sleeping when statuses lie—
A carrot of data, neat and drawn 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the pull request - adding a guard to skip fraud detection for inactive program enrollments, which is a core change reflected across multiple files in the changeset.
✨ 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 fraud-quick-fixes

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8466940 and 15af9ea.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (8 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.

Applied to files:

  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-10-02T22:46:22.739Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2924
File: apps/web/lib/api/conversions/track-lead.ts:7-7
Timestamp: 2025-10-02T22:46:22.739Z
Learning: In apps/web/lib/api/conversions/track-lead.ts, lead events are cached in Redis for 5 minutes (keys: `leadCache:${customer.id}` and `leadCache:${customer.id}:${stringifiedEventName}`) to provide immediate data availability while Tinybird ingestion happens asynchronously. This caching pattern allows for async-only recording without breaking "wait" mode semantics.

Applied to files:

  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (5)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (60-401)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (1)
  • getSubscriptionProductId (4-29)
packages/utils/src/functions/pick.ts (1)
  • pick (3-8)
apps/web/lib/webhook/publish.ts (1)
  • sendWorkspaceWebhook (8-45)
apps/web/lib/webhook/transform.ts (1)
  • transformSaleEventData (81-108)
⏰ 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 (3)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (3)

429-462: LGTM! Commission creation flow properly structured.

The declaration and assignment of createdCommission is correct. The function always returns an object, so the destructuring at line 462 is safe.


498-512: LGTM! Workspace webhook properly uses optional chaining.

The workspace webhook correctly uses createdCommission?.webhookPartner with optional chaining to safely access the partner data.


627-679: LGTM! Lead commission flow properly structured.

The createdCommission handling in the attributeViaPromoCode function follows the same pattern as the main flow. The optional chaining at line 675 correctly guards against undefined values.

Note: Fraud detection is not invoked for lead events in this flow, only for sale events in the main flow. This appears intentional based on the code structure.


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

🧹 Nitpick comments (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (1)

628-668: Note: attributeViaPromoCode uses a different pattern.

The attributeViaPromoCode helper (lines 628-668) still uses the older pattern of extracting webhookPartner via .then() and doesn't invoke detectAndRecordFraudEvent for lead events. This is acceptable since:

  1. The lead creation here is part of a promo code attribution flow
  2. The subsequent sale in the main flow will trigger fraud detection with the updated pattern

If fraud detection for promo-code-attributed leads becomes a requirement, this section would need updating.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6aedb and 8466940.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (4 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (5 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts (3 hunks)
  • apps/web/lib/api/conversions/track-lead.ts (5 hunks)
  • apps/web/lib/api/conversions/track-sale.ts (7 hunks)
  • apps/web/lib/api/fraud/detect-record-fraud-event.ts (2 hunks)
  • apps/web/lib/integrations/shopify/create-sale.ts (5 hunks)
  • apps/web/lib/partners/create-partner-commission.ts (8 hunks)
  • apps/web/lib/zod/schemas/schemas.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.

Applied to files:

  • apps/web/lib/zod/schemas/schemas.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
  • apps/web/lib/integrations/shopify/create-sale.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/lib/api/fraud/detect-record-fraud-event.ts
  • apps/web/lib/partners/create-partner-commission.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts
  • apps/web/lib/api/conversions/track-sale.ts
📚 Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/lib/zod/schemas/schemas.ts
  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/lib/api/fraud/detect-record-fraud-event.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts
  • apps/web/lib/api/conversions/track-sale.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/schemas.ts
📚 Learning: 2025-10-02T22:46:22.739Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2924
File: apps/web/lib/api/conversions/track-lead.ts:7-7
Timestamp: 2025-10-02T22:46:22.739Z
Learning: In apps/web/lib/api/conversions/track-lead.ts, lead events are cached in Redis for 5 minutes (keys: `leadCache:${customer.id}` and `leadCache:${customer.id}:${stringifiedEventName}`) to provide immediate data availability while Tinybird ingestion happens asynchronously. This caching pattern allows for async-only recording without breaking "wait" mode semantics.

Applied to files:

  • apps/web/lib/api/conversions/track-lead.ts
  • apps/web/lib/integrations/shopify/create-sale.ts
  • apps/web/lib/api/conversions/track-sale.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/integrations/shopify/create-sale.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/lib/integrations/shopify/create-sale.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts
  • apps/web/lib/api/conversions/track-sale.ts
📚 Learning: 2025-12-03T09:19:48.164Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3175
File: apps/web/lib/actions/partners/bulk-reject-partner-applications.ts:14-21
Timestamp: 2025-12-03T09:19:48.164Z
Learning: In apps/web/lib/actions/partners/bulk-reject-partner-applications.ts, the bulkRejectPartnerApplicationsAction does not need explicit plan capability checks for fraud operations (when reportFraud is true) because the authorization is handled automatically by the underlying fraud operation functions (resolveFraudGroups, createFraudEvents) or through other automated mechanisms in the system.

Applied to files:

  • apps/web/lib/api/fraud/detect-record-fraud-event.ts
📚 Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.

Applied to files:

  • apps/web/lib/api/fraud/detect-record-fraud-event.ts
📚 Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.

Applied to files:

  • apps/web/lib/api/fraud/detect-record-fraud-event.ts
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/lib/partners/create-partner-commission.ts
🧬 Code graph analysis (6)
apps/web/lib/api/conversions/track-lead.ts (2)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (60-401)
packages/utils/src/functions/pick.ts (1)
  • pick (3-8)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (4)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (1)
  • getSubscriptionProductId (4-29)
packages/utils/src/functions/pick.ts (1)
  • pick (3-8)
apps/web/lib/webhook/publish.ts (1)
  • sendWorkspaceWebhook (8-45)
apps/web/lib/webhook/transform.ts (1)
  • transformSaleEventData (81-108)
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (2)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (60-401)
packages/utils/src/functions/pick.ts (1)
  • pick (3-8)
apps/web/lib/api/fraud/detect-record-fraud-event.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • INACTIVE_ENROLLMENT_STATUSES (26-30)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts (3)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (60-401)
apps/web/lib/api/partners/sync-partner-links-stats.ts (1)
  • syncPartnerLinksStats (5-62)
packages/utils/src/functions/pick.ts (1)
  • pick (3-8)
apps/web/lib/api/conversions/track-sale.ts (1)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (60-401)
⏰ 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 (19)
apps/web/lib/api/fraud/detect-record-fraud-event.ts (1)

22-30: LGTM! Well-placed early guard for inactive enrollments.

The guard correctly skips fraud detection for inactive program enrollments (banned, deactivated, rejected) before performing expensive operations like database queries and rule evaluation. This is a sensible optimization.

apps/web/lib/zod/schemas/schemas.ts (1)

13-15: LGTM! Schema correctly captures the required enrollment status.

Using ProgramEnrollmentSchema.pick({ status: true }) is appropriate - it provides exactly the field needed for the inactive enrollment check while keeping the schema minimal.

apps/web/lib/integrations/shopify/create-sale.ts (2)

134-160: LGTM! Consistent pattern for commission result handling.

The refactor correctly:

  1. Captures the full createPartnerCommission result
  2. Destructures webhookPartner and programEnrollment inside the conditional block where createdCommission is guaranteed to be defined
  3. Passes programEnrollment to fraud detection

182-193: Fraud detection now includes programEnrollment status.

The programEnrollment: pick(programEnrollment, ["status"]) correctly provides the status field needed for the inactive enrollment guard in detectAndRecordFraudEvent.

apps/web/lib/partners/create-partner-commission.ts (2)

109-113: Consistent return shape across all code paths.

All early return paths now correctly include programEnrollment in the return object, ensuring callers always have access to enrollment data regardless of whether a commission was created.


381-399: Return type correctly extended for success and error paths.

Both the success path (lines 381-385) and error path (lines 395-399) return { commission, programEnrollment, webhookPartner }, maintaining a consistent return shape that downstream callers can rely on.

apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (2)

216-243: LGTM! Follows the consistent commission result handling pattern.

The refactoring mirrors the pattern used in other files:

  • Stores the full createPartnerCommission result in createdCommission
  • Destructures webhookPartner and programEnrollment within the conditional block
  • Uses optional chaining for the final webhook payload

265-274: Fraud detection correctly receives programEnrollment status.

The programEnrollment: pick(programEnrollment, ["status"]) ensures the fraud detection flow can evaluate the inactive enrollment guard.

apps/web/lib/api/conversions/track-lead.ts (2)

297-317: LGTM! Consistent commission handling in lead tracking.

The pattern correctly captures the createPartnerCommission result and destructures webhookPartner and programEnrollment for downstream use in fraud detection and webhook payloads.


337-347: Fraud detection correctly integrated with programEnrollment.

The fraud detection call properly includes programEnrollment: pick(programEnrollment, ["status"]) to support the inactive enrollment guard.

apps/web/lib/api/conversions/track-sale.ts (3)

348-362: LGTM! Direct destructuring appropriate for _trackLead.

Since _trackLead doesn't need to pass webhookPartner to an external webhook payload (unlike the main trackLead function), directly destructuring { webhookPartner, programEnrollment } is appropriate here.


548-576: Consistent pattern in _trackSale for commission handling.

The _trackSale function correctly stores the full createPartnerCommission result to support both fraud detection (with programEnrollment) and the workspace webhook (with webhookPartner).


597-607: Fraud detection correctly integrated.

Both _trackLead (line 386) and _trackSale (line 601) now correctly pass programEnrollment: pick(programEnrollment, ["status"]) to enable the inactive enrollment guard.

apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (2)

434-467: LGTM! Main checkout flow correctly updated.

The primary checkoutSessionCompleted flow properly:

  1. Captures the createPartnerCommission result
  2. Destructures webhookPartner and programEnrollment
  3. Passes programEnrollment to fraud detection

489-498: Fraud detection correctly receives programEnrollment.

The programEnrollment: pick(programEnrollment, ["status"]) enables the inactive enrollment guard for checkout-originated sales.

apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts (4)

106-108: Clean type annotation for conditional commission result.

The explicit typing using Awaited<ReturnType<typeof createPartnerCommission>> correctly captures the resolved promise type and the | undefined union properly handles the conditional assignment path.


111-127: Good refactor to derive partner/enrollment data from commission result.

Storing the commission result and destructuring webhookPartner and programEnrollment makes the data flow explicit and aligns with the pattern used in other call sites per the PR's cross-file changes.


128-158: Well-structured background task execution with proper fraud detection guard.

Using Promise.allSettled ensures all background operations are attempted even if one fails. The webhookPartner && guard on line 147 appropriately skips fraud detection when partner data is unavailable, and passing programEnrollment.status (line 151) supports the PR objective of skipping fraud detection for inactive enrollments.

Note that errors from these settled promises are silently dropped, which is typical for fire-and-forget patterns but may make debugging harder if issues arise.


161-175: Correct use of optional chaining for webhook partner data.

The createdCommission?.webhookPartner safely handles cases where no commission was created (when the link has no programId/partnerId), ensuring the workspace webhook still fires for all lead events.

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/stripe/integration/webhook/checkout-session-completed.ts (1)

484-493: Guard against null programEnrollment before calling pick.

The fraud detection is guarded by webhookPartner && but line 488 uses pick(programEnrollment, ["status"]) without verifying that programEnrollment exists. According to the context from create-partner-commission.ts, programEnrollment can be null or undefined in error cases, which would cause pick to throw since it expects an object.

Apply this diff to add a null check:

-        webhookPartner &&
+        webhookPartner && programEnrollment &&
           detectAndRecordFraudEvent({
             program: { id: link.programId },
             partner: pick(webhookPartner, ["id", "email", "name"]),
             programEnrollment: pick(programEnrollment, ["status"]),
             customer: pick(customer, ["id", "email", "name"]),
             link: pick(link, ["id"]),
             click: pick(saleData, ["url", "referer"]),
             event: { id: saleData.event_id },
           }),

Alternatively, if fraud detection should be called even without enrollment data, adjust the payload:

         webhookPartner &&
           detectAndRecordFraudEvent({
             program: { id: link.programId },
             partner: pick(webhookPartner, ["id", "email", "name"]),
-            programEnrollment: pick(programEnrollment, ["status"]),
+            programEnrollment: programEnrollment ? pick(programEnrollment, ["status"]) : undefined,
             customer: pick(customer, ["id", "email", "name"]),
             link: pick(link, ["id"]),
             click: pick(saleData, ["url", "referer"]),
             event: { id: saleData.event_id },
           }),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8466940 and 15af9ea.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (8 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.
📚 Learning: 2025-12-08T09:44:28.429Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3200
File: apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts:55-73
Timestamp: 2025-12-08T09:44:28.429Z
Learning: In apps/web/lib/api/fraud/detect-duplicate-payout-method-fraud.ts, the fraud event creation logic intentionally generates self-referential fraud events (where partnerId equals duplicatePartnerId) for partners with duplicate payout methods. This is by design to create raw events for all partners involved in a duplicate payout method scenario, regardless of whether they reference themselves.

Applied to files:

  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-10-02T22:46:22.739Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2924
File: apps/web/lib/api/conversions/track-lead.ts:7-7
Timestamp: 2025-10-02T22:46:22.739Z
Learning: In apps/web/lib/api/conversions/track-lead.ts, lead events are cached in Redis for 5 minutes (keys: `leadCache:${customer.id}` and `leadCache:${customer.id}:${stringifiedEventName}`) to provide immediate data availability while Tinybird ingestion happens asynchronously. This caching pattern allows for async-only recording without breaking "wait" mode semantics.

Applied to files:

  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (5)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (60-401)
apps/web/app/(ee)/api/stripe/integration/webhook/utils/get-subscription-product-id.ts (1)
  • getSubscriptionProductId (4-29)
packages/utils/src/functions/pick.ts (1)
  • pick (3-8)
apps/web/lib/webhook/publish.ts (1)
  • sendWorkspaceWebhook (8-45)
apps/web/lib/webhook/transform.ts (1)
  • transformSaleEventData (81-108)
⏰ 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 (3)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (3)

429-462: LGTM! Commission creation flow properly structured.

The declaration and assignment of createdCommission is correct. The function always returns an object, so the destructuring at line 462 is safe.


498-512: LGTM! Workspace webhook properly uses optional chaining.

The workspace webhook correctly uses createdCommission?.webhookPartner with optional chaining to safely access the partner data.


627-679: LGTM! Lead commission flow properly structured.

The createdCommission handling in the attributeViaPromoCode function follows the same pattern as the main flow. The optional chaining at line 675 correctly guards against undefined values.

Note: Fraud detection is not invoked for lead events in this flow, only for sale events in the main flow. This appears intentional based on the code structure.

@steven-tey
Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@steven-tey steven-tey merged commit d0daa2e into main Dec 17, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the fraud-quick-fixes branch December 17, 2025 19:37
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.

3 participants