Skip to content

Improve net-new revenue bounty logic - #3986

Merged
steven-tey merged 1 commit into
mainfrom
improve-revenue-bounty
Jun 4, 2026
Merged

Improve net-new revenue bounty logic#3986
steven-tey merged 1 commit into
mainfrom
improve-revenue-bounty

Conversation

@steven-tey

@steven-tey steven-tey commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Performance bounties now validate eligibility based on customer first sale date before awarding.
  • Improvements

    • Enhanced customer identity tracking in partner workflow metrics with additional identification fields.

@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Jun 4, 2026 8:10pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9bbc08c-6496-4261-b26f-2a4c6b75f0dd

📥 Commits

Reviewing files that changed from the base of the PR and between bb836cc and c9a2515.

📒 Files selected for processing (6)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts
  • apps/web/lib/api/conversions/track-sale.ts
  • apps/web/lib/api/workflows/execute-complete-bounty-workflow.ts
  • apps/web/lib/integrations/shopify/create-sale.ts
  • apps/web/lib/types.ts

📝 Walkthrough

Walkthrough

This PR extends the workflow identity system to track customer identity and enforces eligibility constraints on performance bounties. A WorkflowIdentity type gains two optional fields (customerId and customerFirstSaleAt), all sale event handlers and integrations propagate these fields to workflows, and the bounty workflow filters out existing customers from new-customer performance bounties.

Changes

Customer Identity and Bounty Eligibility

Layer / File(s) Summary
WorkflowIdentity type extension
apps/web/lib/types.ts
WorkflowIdentity interface extended with optional customerId and customerFirstSaleAt fields to support customer identity tracking in workflows.
Customer identity propagation across sale pathways
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts, apps/web/lib/api/conversions/track-sale.ts, apps/web/lib/integrations/shopify/create-sale.ts
Stripe webhooks (checkout completion, invoice paid), direct sale tracking, and Shopify integration all now include customerId and customerFirstSaleAt when triggering partnerMetricsUpdated workflows. Metrics field ordering is also normalized.
Performance bounty eligibility filtering
apps/web/lib/api/workflows/execute-complete-bounty-workflow.ts
executeCompleteBountyWorkflow reads the customer identity fields and skips bounty award execution for new-customer-only performance bounties when the customer's first sale predates the bounty start date.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • pepeladeira

Poem

🐰 A rabbit hops through customer tracks,
Recording first-sale facts and dates,
Then bounties bloom for new friends only—
Existing ones no longer eligible, fairly,
The workflow flows, identity intact! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Improve net-new revenue bounty logic' is partially related to the changeset. While the PR does modify bounty logic (in execute-complete-bounty-workflow.ts), the broader changeset involves adding customer identity fields and reordering metrics across multiple sale/workflow tracking files, which is not adequately summarized by the title. Consider a more comprehensive title that captures the main focus, such as 'Add customer identity fields and performance bounty eligibility check' or similar, to better represent the full scope of changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve-revenue-bounty

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.

@steven-tey

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review, any breaking changes? does the logic changes look good?

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== execute-complete-bounty-workflow.ts ==="
cat apps/web/lib/api/workflows/execute-complete-bounty-workflow.ts

echo ""
echo "=== types.ts (WorkflowIdentity section) ==="
rg -n "WorkflowIdentity|customerId|customerFirstSaleAt" apps/web/lib/types.ts -A 2 -B 2

echo ""
echo "=== track-sale.ts (executeWorkflows call) ==="
rg -n "executeWorkflows|customerFirstSaleAt|customerId|identity" apps/web/lib/api/conversions/track-sale.ts -A 3 -B 3

echo ""
echo "=== checkout-session-completed.ts (executeWorkflows call) ==="
rg -n "executeWorkflows|customerFirstSaleAt|customerId|identity" "apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts" -A 3 -B 3

echo ""
echo "=== invoice-paid.ts (executeWorkflows call) ==="
rg -n "executeWorkflows|customerFirstSaleAt|customerId|identity" "apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts" -A 3 -B 3

echo ""
echo "=== shopify/create-sale.ts ==="
rg -n "executeWorkflows|customerFirstSaleAt|customerId|identity" apps/web/lib/integrations/shopify/create-sale.ts -A 3 -B 3

echo ""
echo "=== bounty performanceScope schema/type ==="
rg -n "performanceScope" --type ts -A 2 -B 2

echo ""
echo "=== startsAt on bounty ==="
rg -n "startsAt" --type ts -A 2 -B 2 | head -80

echo ""
echo "=== AwardBounty action ==="
rg -n "AwardBounty" --type ts -A 3 -B 3

echo ""
echo "=== customer.firstSaleAt usage ==="
rg -n "firstSaleAt" --type ts -A 2 -B 2

Length of output: 68737



@steven-tey: Happy to do a full review! Here's my analysis:


Breaking Changes

No breaking changes. Here's why:

  • customerId and customerFirstSaleAt are both declared optional (?) on WorkflowIdentity — existing call sites that don't supply them are unaffected.

  • The new gate in executeCompleteBountyWorkflow only fires when all three conditions are true simultaneously:

    1. bounty.performanceScope === "new"
    2. customerFirstSaleAt is truthy (not undefined/null)
    3. customerFirstSaleAt < bounty.startsAt

    Since customerFirstSaleAt is optional, any pre-existing code path that doesn't pass it simply leaves condition 2 false — the gate is silently skipped, which is the safe default (bounty proceeds as before).


Logic Assessment ✅

The core gate in execute-complete-bounty-workflow.ts:

if (
  bounty.performanceScope === "new" &&
  customerFirstSaleAt &&
  customerFirstSaleAt < bounty.startsAt
) {
  console.log(`Bounty ... is for net-new revenue only and partner ... referred customer ... before the bounty started, skipping...`);
  return;
}

This is semantically correct: a "new" scope bounty should only reward revenue from customers whose first ever purchase happened after the bounty started. The comparison of customerFirstSaleAt against bounty.startsAt is the right signal.

The ?? new Date() fallback is correct too:

  • If customer.firstSaleAt is null, this is a first-time paying customer → customerFirstSaleAt = new Date() (approximately "right now").
  • For an active bounty, bounty.startsAt is always in the past (it's already started), so now >= bounty.startsAt → the gate does not skip → bounty is correctly awarded. ✅

Call site coverage is complete — all four sale recording entry points are updated:

  • track-sale.ts (API)
  • checkout-session-completed.ts (Stripe)
  • invoice-paid.ts (Stripe)
  • shopify/create-sale.ts

Consistency with existing logic: trigger-draft-bounty-submissions.ts already has if (bounty.performanceScope === "new") return false, so the "new scope = no draft submissions" concept already existed. This PR correctly extends that to the real-time workflow execution path.


Minor Observations

  1. Tiny timestamp drift: The new Date() written to the DB as firstSaleAt and the new Date() passed to customerFirstSaleAt are two separate instantiations (slightly different clock reads), but this is sub-millisecond and has no practical consequence on the < comparison.

  2. bounty.startsAt null safety: The bounty.startsAt field is typed as non-nullable (z.date() in the response schema) and validate-bounty.ts always sets it to new Date() if omitted. The comparison customerFirstSaleAt < bounty.startsAt is safe.

  3. Metric field reordering (conversions before saleAmount): Purely cosmetic — no behavioral impact.


Overall: The changes are well-scoped, non-breaking, and the logic correctly implements net-new revenue bounty gating. LGTM! 🐇


🧠 Learnings used
Learnt from: steven-tey
Repo: dubinc/dub PR: 0
File: :0-0
Timestamp: 2026-06-03T20:04:17.294Z
Learning: In dubinc/dub PR `#3824`, replacing `context.sale.productId` with `context.sale.products` (array) in `checkout-session-completed.ts` and `invoice-paid.ts` is NOT a breaking change for existing reward conditions. The updated `create-partner-commission/route.ts` workflow mediates this: when `context.sale.products` is present and a `sale.productId` modifier is detected, the workflow iterates through each product, reconstructs `productId` from each product entry, and calls `determinePartnerReward` with a per-product context. So `evaluate-reward-conditions.ts` still receives `productId` correctly — it just comes from the `products` array iteration rather than a top-level `context.sale.productId`. Do not flag this as a breaking change in future reviews.

@steven-tey
steven-tey merged commit 34821bc into main Jun 4, 2026
12 checks passed
@steven-tey
steven-tey deleted the improve-revenue-bounty branch June 4, 2026 20:30
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