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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Sep 22, 2025

Summary by CodeRabbit

  • New Features

    • lead.created and sale.created webhooks now include a partner object when available, with totals (clicks, leads, conversions, sales, sale amount, commissions).
    • Standardized partner payload across lead, sale, and commission webhooks.
    • Shopify sale webhooks are enriched to include partner details when commissions are created.
  • Refactor

    • Unified partner schema across webhooks for consistency.
    • Partner customers query now uses a fixed page size of 100.

@vercel
Copy link
Contributor

vercel bot commented Sep 22, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 22, 2025 2:05am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Walkthrough

Adds a WebhookPartner schema/type and propagates an optional partner payload through commission creation and webhook flows. createPartnerCommission now returns { commission, webhookPartner }. Multiple webhook emitters (lead/sale) and integrations are updated to include partner in payloads. Schemas adapted to accept the new partner shape.

Changes

Cohort / File(s) Summary
Stripe integration webhooks
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/app/(ee)/api/stripe/integration/webhook/utils.ts
Capture webhookPartner from commission creation and include partner in lead/sale webhook payloads; add WebhookPartner imports.
Conversions API
apps/web/lib/api/conversions/track-lead.ts, apps/web/lib/api/conversions/track-sale.ts
Thread webhookPartner through lead/sale tracking; enrich workspace webhook payloads with partner; import WebhookPartner.
Partners commission core
apps/web/lib/partners/create-partner-commission.ts
Add constructWebhookPartner; change createPartnerCommission to return { commission, webhookPartner } in all paths; unify webhook partner payload construction; sync totals once.
Shopify integration
apps/web/lib/integrations/shopify/create-sale.ts
Defer sale webhook until after commission creation; include partner via webhookPartner; pass linkId; import WebhookPartner.
Workflows and actions
apps/web/lib/actions/partners/approve-bounty-submission.ts, apps/web/lib/api/workflows/execute-award-bounty-action.ts
Adjust to destructure { commission } from createPartnerCommission return.
Schemas and types
apps/web/lib/types.ts, apps/web/lib/webhook/schemas.ts, apps/web/lib/zod/schemas/partners.ts, apps/web/lib/zod/schemas/commissions.ts
Add WebhookPartner type and WebhookPartnerSchema; include optional partner in lead/sale webhook schemas; switch commission webhook partner field to WebhookPartnerSchema; remove PARTNER_CUSTOMERS_MAX_PAGE_SIZE and inline 100.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Src as Source (Stripe/Shopify/API)
  participant Svc as App Service
  participant Com as createPartnerCommission
  participant WH as Webhook Publisher

  rect rgba(200,230,255,0.25)
    note over Svc: Track Lead/Sale
    Src->>Svc: Event (lead/sale)
    Svc->>Com: createPartnerCommission(link, program, partner, ...)
    Com-->>Svc: { commission, webhookPartner }
  end

  alt commission created or partner context present
    Svc->>WH: Publish lead.created/sale.created { ..., partner: webhookPartner }
  else no partner context
    Svc->>WH: Publish lead.created/sale.created { ..., partner: null/omitted }
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • devkiran
  • TWilson023

Poem

I thump with glee, my paws alight,
A partner hops into the webhook night.
Commissions bloom, totals in tow,
Leads and sales now proudly show.
Carrots count clicks—crunchy art—
Every payload carries a partner’s heart. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 "Add WebhookPartner to sale & lead webhooks" is clear, concise, and directly summarizes the primary change in the changeset—adding a WebhookPartner payload to sale and lead webhook events across the codebase—making it easy for teammates scanning history to understand the main intention.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch webhook-partner

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/lib/actions/partners/approve-bounty-submission.ts (1)

68-76: Add idempotency key (eventId) + context; migrate remaining createPartnerCommission call sites

Destructuring { commission } here is correct — add an idempotency key and context to prevent duplicate commissions. Several other call sites still assign the promise result; migrate them to the new return shape and add eventId/context.

Affected call sites found (verify these lines in your branch):

  • apps/web/lib/integrations/shopify/create-sale.ts: ~132
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts: ~361
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts: ~106
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts: ~209
  • apps/web/lib/api/conversions/track-lead.ts: ~305
  • apps/web/lib/api/conversions/track-sale.ts: ~517

Apply this patch in approve-bounty-submission.ts:

-    const { commission } = await createPartnerCommission({
+    const { commission } = await createPartnerCommission({
       event: "custom",
       partnerId: bountySubmission.partnerId,
       programId: bountySubmission.programId,
+      eventId: submissionId,
       amount: finalRewardAmount,
       quantity: 1,
       user,
+      context: { source: "bounty_submission", submissionId },
       description: `Commission for successfully completed "${bounty.name}" bounty.`,
     });

rg ended with a PCRE2 error during the scan — re-run the search with a fixed pattern to ensure no other occurrences remain.

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

57-74: Audit call-sites for new return shape — update to destructure { commission, webhookPartner }

Several call-sites still either assign the full return to a variable or only extract commission; update all to destructure both values or explicitly use webhookPartner.

  • apps/web/lib/integrations/shopify/create-sale.ts:132 — const createdCommission = await createPartnerCommission(...)
  • apps/web/lib/api/conversions/track-lead.ts:305 — const createdCommission = await createPartnerCommission(...)
  • apps/web/lib/api/conversions/track-sale.ts:517 — const createdCommission = await createPartnerCommission(...)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts:209 — const createdCommission = await createPartnerCommission(...)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts:361 — const createdCommission = await createPartnerCommission(...)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts:106 — const createdCommission = await createPartnerCommission(...)
  • apps/web/lib/actions/partners/approve-bounty-submission.ts:68 — const { commission } = await createPartnerCommission(...)
  • apps/web/lib/api/workflows/execute-award-bounty-action.ts:163 — const { commission } = await createPartnerCommission(...)

Change to: const { commission, webhookPartner } = await createPartnerCommission(...) and update any downstream uses of createdCommission.* accordingly.

🧹 Nitpick comments (5)
apps/web/lib/actions/partners/approve-bounty-submission.ts (1)

59-66: Use nullish check (not falsy) for reward amount; optionally assert > 0.

Avoid rejecting a legitimate 0 by accident (and make intent explicit).

Apply this diff:

-    if (!finalRewardAmount) {
+    if (finalRewardAmount == null) {
       throw new Error(
         "Reward amount is required to approve the bounty submission.",
       );
     }
+    if (finalRewardAmount <= 0) {
+      throw new Error("Reward amount must be greater than 0.");
+    }
apps/web/lib/zod/schemas/partners.ts (1)

401-401: Avoid magic number for page size (use existing constant)

Reuse the already‑defined PARTNERS_MAX_PAGE_SIZE for consistency and easier future updates.

-  .merge(getPaginationQuerySchema({ pageSize: 100 }));
+  .merge(getPaginationQuerySchema({ pageSize: PARTNERS_MAX_PAGE_SIZE }));
apps/web/lib/api/workflows/execute-award-bounty-action.ts (1)

163-171: Pass currency for custom commissions (prevent null/undefined currency)

createPartnerCommission writes currency to the commission. For "custom" events this call doesn't provide one; if the DB column or formatter expects a non‑null currency, this can break. Consider passing the program’s currency (fallback to "usd").

-  const { commission } = await createPartnerCommission({
+  const { commission } = await createPartnerCommission({
     event: "custom",
     partnerId,
     programId: bounty.programId,
     amount: bounty.rewardAmount,
     quantity: 1,
     description: `Commission for successfully completed "${bounty.name}" bounty.`,
     skipWorkflow: true,
+    currency: bounty.program?.currency ?? "usd",
   });
apps/web/lib/api/conversions/track-sale.ts (1)

381-393: Also include partner on lead.created in this path for consistency

In _trackLead, you already create a commission but don’t thread webhookPartner into the lead webhook. Recommend mirroring the Stripe flows for consistent payloads.

       // Create partner commission and execute workflows
       if (link.programId && link.partnerId && customer) {
-        await createPartnerCommission({
+        let webhookPartner: WebhookPartner | undefined;
+        const createdCommission = await createPartnerCommission({
           event: "lead",
           programId: link.programId,
           partnerId: link.partnerId,
           linkId: link.id,
           eventId: leadEventData.event_id,
           customerId: customer.id,
           quantity: 1,
           context: {
             customer: {
               country: customer.country,
             },
           },
         });
+        webhookPartner = createdCommission?.webhookPartner;
 
         await executeWorkflows({
           trigger: WorkflowTrigger.leadRecorded,
           context: {
             programId: link.programId,
             partnerId: link.partnerId,
             current: {
               leads: 1,
             },
           },
         });
       }
 
       // Send workspace webhook
-      const webhookPayload = transformLeadEventData({
+      const webhookPayload = transformLeadEventData({
         ...leadEventData,
         link,
         customer,
+        partner: webhookPartner,
       });
apps/web/lib/partners/create-partner-commission.ts (1)

46-55: Relax links typing and shape the payload to schema.
getProgramEnrollmentOrThrow may not guarantee links; declare it optional. Also return only fields required by WebhookPartnerSchema to avoid accidental extra props before Zod strips them.

-const constructWebhookPartner = (
-  programEnrollment: ProgramEnrollment & { partner: Partner; links: Link[] },
-  { totalCommissions }: { totalCommissions: number } = { totalCommissions: 0 },
-) => {
-  return {
-    ...programEnrollment.partner,
-    ...aggregatePartnerLinksStats(programEnrollment.links),
-    totalCommissions: totalCommissions || programEnrollment.totalCommissions,
-  };
-};
+const constructWebhookPartner = (
+  programEnrollment: ProgramEnrollment & {
+    partner: Pick<
+      Partner,
+      "id" | "name" | "email" | "image" | "payoutsEnabledAt" | "country"
+    >;
+    links?: Pick<Link, "clicks" | "leads" | "conversions" | "sales" | "saleAmount">[];
+  },
+  { totalCommissions }: { totalCommissions: number } = { totalCommissions: 0 },
+) => {
+  const totals = aggregatePartnerLinksStats(programEnrollment.links);
+  return {
+    id: programEnrollment.partner.id,
+    name: programEnrollment.partner.name,
+    email: programEnrollment.partner.email,
+    image: programEnrollment.partner.image,
+    payoutsEnabledAt: programEnrollment.partner.payoutsEnabledAt,
+    country: programEnrollment.partner.country,
+    ...totals,
+    totalCommissions: totalCommissions || programEnrollment.totalCommissions,
+  };
+};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd57225 and 8fbc371.

📒 Files selected for processing (13)
  • apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (5 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (4 hunks)
  • apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts (4 hunks)
  • apps/web/lib/actions/partners/approve-bounty-submission.ts (1 hunks)
  • apps/web/lib/api/conversions/track-lead.ts (4 hunks)
  • apps/web/lib/api/conversions/track-sale.ts (4 hunks)
  • apps/web/lib/api/workflows/execute-award-bounty-action.ts (1 hunks)
  • apps/web/lib/integrations/shopify/create-sale.ts (4 hunks)
  • apps/web/lib/partners/create-partner-commission.ts (11 hunks)
  • apps/web/lib/types.ts (2 hunks)
  • apps/web/lib/webhook/schemas.ts (3 hunks)
  • apps/web/lib/zod/schemas/commissions.ts (2 hunks)
  • apps/web/lib/zod/schemas/partners.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-25T17:33:45.072Z
Learnt from: devkiran
PR: dubinc/dub#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/api/conversions/track-lead.ts
  • apps/web/lib/partners/create-partner-commission.ts
🧬 Code graph analysis (13)
apps/web/lib/types.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • WebhookPartnerSchema (372-388)
apps/web/lib/webhook/schemas.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • WebhookPartnerSchema (372-388)
apps/web/lib/api/workflows/execute-award-bounty-action.ts (1)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/lib/api/conversions/track-lead.ts (2)
apps/web/lib/types.ts (1)
  • WebhookPartner (398-398)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/lib/zod/schemas/partners.ts (1)
apps/web/lib/zod/schemas/misc.ts (1)
  • getPaginationQuerySchema (31-54)
apps/web/lib/partners/create-partner-commission.ts (1)
apps/web/lib/partners/aggregate-partner-links-stats.ts (1)
  • aggregatePartnerLinksStats (3-38)
apps/web/lib/actions/partners/approve-bounty-submission.ts (1)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts (2)
apps/web/lib/types.ts (1)
  • WebhookPartner (398-398)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/lib/api/conversions/track-sale.ts (2)
apps/web/lib/types.ts (1)
  • WebhookPartner (398-398)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/lib/integrations/shopify/create-sale.ts (3)
apps/web/lib/types.ts (1)
  • WebhookPartner (398-398)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/lib/webhook/transform.ts (1)
  • transformSaleEventData (81-108)
apps/web/lib/zod/schemas/commissions.ts (1)
apps/web/lib/zod/schemas/partners.ts (1)
  • WebhookPartnerSchema (372-388)
apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts (2)
apps/web/lib/types.ts (1)
  • WebhookPartner (398-398)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts (3)
apps/web/lib/types.ts (1)
  • WebhookPartner (398-398)
apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts (1)
  • getSubscriptionProductId (218-241)
apps/web/lib/partners/create-partner-commission.ts (1)
  • createPartnerCommission (57-368)
⏰ 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 (22)
apps/web/lib/types.ts (1)

72-73: Type alias and import look good

Introducing WebhookPartner via WebhookPartnerSchema is consistent with the new webhook payloads. No issues spotted.

Also applies to: 398-399

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

372-389: WebhookPartnerSchema shape looks correct

The picked fields and totals align with webhook usage. One small thing to double-check: payoutsEnabledAt is z.date().nullable(); ensure downstream webhook serialization expects a Date (will JSON‑stringify) or explicitly casts to ISO string if your OpenAPI/docs expect string.

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

7-7: Partner propagation to sale webhook is correct

Capturing webhookPartner from createPartnerCommission and threading it into transformSaleEventData matches the new schema. Looks good.

Also applies to: 206-230, 256-257

apps/web/app/(ee)/api/stripe/integration/webhook/utils.ts (1)

8-9: Lead webhook now includes partner — good parity with sale flow

Storing webhookPartner and adding it to the lead payload is consistent with the updated schemas.

Also applies to: 104-121, 133-135

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

514-539: Sale webhook partner wiring LGTM

Assigning webhookPartner from the commission result and passing it through to transformSaleEventData is consistent and safe when undefined.

Also applies to: 559-561

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

13-14: End‑to‑end partner enrichment looks solid

You capture webhookPartner once and include it in both lead.created (when applicable) and sale.created payloads. Implementation matches schema changes.

Also applies to: 353-382, 428-445

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

6-9: Schemas updated correctly to accept partner

partner: WebhookPartnerSchema.nullish() on lead and sale events is the right contract for optional inclusion.

If not already done, ensure transformLeadEventData and transformSaleEventData accept and forward an optional partner field.

Also applies to: 29-31, 39-41

apps/web/lib/zod/schemas/commissions.ts (2)

6-6: Unifying on WebhookPartnerSchema looks good.
Importing WebhookPartnerSchema keeps partner payload consistent across webhooks.


47-49: Commission webhook should always include partner — schema change SGTM.
File: apps/web/lib/zod/schemas/commissions.ts (lines 47–49). Keeping partner required matches commission.created semantics. Repo search returned no matches for emitters including partner; confirm all commission.created emitters send webhookPartner or make partner nullable / add fallback handling to avoid parse failures.

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

9-9: Type import addition is correct.
WebhookPartner is the right type for enrichment.


302-303: Local webhookPartner capture is fine.
Using undefined aligns with .nullish() schemas downstream.


305-320: Correctly adapts to new return shape.
Capturing { webhookPartner } from createPartnerCommission is right, including when no commission is created.


333-345: No change required — partner is preserved.
transformLeadEventData spreads ...lead (which includes the camelCased partner) into the parsed object and leadWebhookEventSchema defines partner as nullable, so the partner field will be retained.

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

6-6: WebhookPartner import is appropriate.
Keeps sale webhook enrichment typed.


130-131: Capturing webhookPartner ahead of webhook emission is good.
This ensures enrichment is available for the final payload.


132-150: Adapts to new return shape correctly.
Storing createdCommission?.webhookPartner is correct.

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

6-10: Type imports OK.
Using Link, Partner, ProgramEnrollment for the constructor signature is appropriate.


102-106: Early-return branches now return webhookPartner — good.
This ensures downstream webhooks can still enrich with partner even when no commission is created.

Also applies to: 139-143, 168-172, 183-187, 200-204


261-270: Sync before construct is correct.
Fetching totalCommissions and feeding it into constructWebhookPartner avoids stale totals in emitted payloads.


299-307: Using CommissionWebhookSchema with constructed partner is correct.
Validates payload and standardizes fields.


350-353: Return shape change acknowledged.
Returning { commission, webhookPartner } enables callers to enrich their own webhooks.


363-366: Error path still provides webhookPartner.
Good fallback so callers can emit partner context even on failure.

@steven-tey steven-tey merged commit faf4050 into main Sep 22, 2025
9 checks passed
@steven-tey steven-tey deleted the webhook-partner branch September 22, 2025 04:45
@coderabbitai coderabbitai bot mentioned this pull request Oct 17, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 5, 2025
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