-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add WebhookPartner to sale & lead webhooks #2867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds a WebhookPartner schema/type and propagates an optional partner payload through commission creation and webhook flows. Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 sitesDestructuring
{ 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 usewebhookPartner.
- 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_SIZEfor 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)
createPartnerCommissionwritescurrencyto 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 consistencyIn
_trackLead, you already create a commission but don’t threadwebhookPartnerinto 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: Relaxlinkstyping and shape the payload to schema.
getProgramEnrollmentOrThrowmay not guaranteelinks; declare it optional. Also return only fields required byWebhookPartnerSchemato 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
📒 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.tsapps/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 goodIntroducing
WebhookPartnerviaWebhookPartnerSchemais 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 correctThe picked fields and totals align with webhook usage. One small thing to double-check:
payoutsEnabledAtisz.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 correctCapturing
webhookPartnerfromcreatePartnerCommissionand threading it intotransformSaleEventDatamatches 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 flowStoring
webhookPartnerand 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 LGTMAssigning
webhookPartnerfrom the commission result and passing it through totransformSaleEventDatais 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 solidYou capture
webhookPartneronce 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
transformLeadEventDataandtransformSaleEventDataaccept and forward an optionalpartnerfield.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). Keepingpartnerrequired matchescommission.createdsemantics. Repo search returned no matches for emitters includingpartner; confirm allcommission.createdemitters sendwebhookPartneror makepartnernullable / add fallback handling to avoid parse failures.apps/web/lib/api/conversions/track-lead.ts (4)
9-9: Type import addition is correct.
WebhookPartneris the right type for enrichment.
302-303: LocalwebhookPartnercapture is fine.
Usingundefinedaligns with.nullish()schemas downstream.
305-320: Correctly adapts to new return shape.
Capturing{ webhookPartner }fromcreatePartnerCommissionis 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: CapturingwebhookPartnerahead of webhook emission is good.
This ensures enrichment is available for the final payload.
132-150: Adapts to new return shape correctly.
StoringcreatedCommission?.webhookPartneris correct.apps/web/lib/partners/create-partner-commission.ts (6)
6-10: Type imports OK.
UsingLink,Partner,ProgramEnrollmentfor 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.
FetchingtotalCommissionsand feeding it intoconstructWebhookPartneravoids 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 provideswebhookPartner.
Good fallback so callers can emit partner context even on failure.
Summary by CodeRabbit
New Features
Refactor