Add API endpoint for partner deactivation#3414
Conversation
Updated the partner deactivation email template to handle cases where the entire partner program is deactivated, not just individual partnerships. The email content now conditionally renders different messages and instructions based on whether the deactivation is for the whole program or a single partner.
Introduces logic to detect and warn users when changing or canceling plans would result in losing partner program access. Adds a confirmation modal for such cases in subscription and upgrade flows, and centralizes partner access checks in a new utility.
Added logic to display a warning in the failed payment email if the workspace has an active partner program and a qualifying plan. Also updated the email copy for clarity and improved user guidance. The webhook handler now selects additional workspace fields needed for this logic.
Removed the specific reference to 'Dub' in the failed payment attempt heading to make the message more generic.
Introduces an isSubmitting state to disable and show loading on the 'Continue' button during async confirmation. Updates the onConfirm prop to support async functions and ensures the modal closes before confirmation logic executes.
Removed an extra space after '3rd' in the payment attempt heading of the failed payment email template.
The modal is no longer closed before the onConfirm action completes, ensuring the confirmation process is not interrupted and improving user experience.
Updated the plan change confirmation modal to present partner program impact details as a concise bulleted list. Clarified messaging and integrated the pending payouts link directly into the list for improved readability.
- Add deactivatedAt column to Program schema to track deactivation timestamp - Hide deactivated programs from partner profile endpoint - Update deactivation email subject to differentiate between program-wide and individual partnership deactivations
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/web/lib/api/partners/deactivate-partner.ts`:
- Around line 101-116: The qstash.publishJSON call in deactivatePartner can fail
silently and leave a partner deactivated without any cleanup job queued; wrap
the qstash.publishJSON invocation in a try-catch inside deactivatePartner and on
error log the failure and persist a retry entry (outbox) or set a DB flag to
trigger a background retry, so the /api/cron/partners/deactivate job can be
retried later; specifically update the block that calls qstash.publishJSON (and
its surrounding success/error branches) to catch exceptions, record the failed
job (e.g., insert a retry record referencing programId and partner.id) and
ensure the scheduled job is retriable from that persisted state (mirror the
try-catch pattern used in schedule-campaigns.ts).
🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/partners.ts (1)
824-825: Adding a refinement here would duplicate runtime validation without improving OpenAPI docs.The route already validates this requirement via
throwIfNoPartnerIdOrTenantId()on line 19. Additionally, the schema documents the constraint in thetenantIdfield description. A refinement alone won't enhance the OpenAPI schema—zod-openapi cannot auto-translaterefine()into OpenAPI constraints and requires manual.meta({ override: ... })configuration to express the constraint in the generated schema. If strict schema-level validation is desired, add both the refinement and a corresponding.meta()override; otherwise, the current setup is sufficient.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/web/app/`(ee)/api/partners/deactivate/route.ts:
- Around line 26-47: The current handler resolves partnerId from tenantId only
when partnerId is missing, which allows a mismatch when both tenantId and
partnerId are provided; update the logic around tenantId/partnerId in the route
handler so that if tenantId is present you always load the programEnrollment via
prisma.programEnrollment.findUnique({ where: { tenantId_programId: { tenantId,
programId } } }) and then (1) if no enrollment throw a DubApiError not_found,
and (2) if partnerId was provided and enrollment.partnerId !== partnerId throw a
DubApiError (bad_request or conflict) indicating the tenantId/partnerId
mismatch; otherwise set partnerId = enrollment.partnerId and continue. Ensure
references to tenantId, partnerId, prisma.programEnrollment, and DubApiError are
used so the change is made in the correct place.
🧹 Nitpick comments (1)
apps/web/tests/partners/deactivate-partner.test.ts (1)
14-14: Consider adding negative test cases.The current tests cover the happy path well. Consider adding tests for edge cases such as:
- Attempting to deactivate an already deactivated partner
- Attempting to deactivate a non-existent partner
- Attempting to deactivate a banned partner (should fail per the API logic)
|
@coderabbitai full review, any breaking changes? |
🧩 Analysis chain🏁 Script executed: Length of output: 12544
✅ No Breaking ChangesAll changes are backward compatible: The
Summary of ChangesAdditions (non-breaking):
Internal Refactoring:
Architecture Improvements✅ DRY principle: Deactivation logic centralized in one place Minor ObservationsThe test utility ✅ Actions performedReview triggered.
|
Summary by CodeRabbit
New Features
Updates
Tests