fix: correctly orphan envelopes and stripe cancel on delete (#2967)

This commit is contained in:
David Nguyen
2026-06-09 15:52:14 +10:00
committed by GitHub
parent 3c0345f755
commit 8c11266747
7 changed files with 488 additions and 91 deletions
@@ -1,10 +1,6 @@
import {
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP,
ORGANISATION_USER_ACCOUNT_TYPE,
} from '@documenso/lib/constants/organisations';
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { jobs } from '@documenso/lib/jobs/client';
import { orphanEnvelopes } from '@documenso/lib/server-only/envelope/orphan-envelopes';
import { deleteOrganisation } from '@documenso/lib/server-only/organisation/delete-organisation';
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
import { prisma } from '@documenso/prisma';
@@ -57,35 +53,5 @@ export const deleteOrganisationRoute = authenticatedProcedure
});
}
// Orphan all envelopes to get rid of foreign key constraints.
await Promise.all(organisation.teams.map(async (team) => orphanEnvelopes({ teamId: team.id })));
await prisma.$transaction(async (tx) => {
await tx.account.deleteMany({
where: {
type: ORGANISATION_USER_ACCOUNT_TYPE,
provider: organisation.id,
},
});
await tx.organisation.delete({
where: {
id: organisation.id,
},
});
});
// If the organisation has a Stripe subscription, schedule it to be
// cancelled at the end of the current billing period. The job runs
// asynchronously so a Stripe outage doesn't block deletion, and is
// retried by the job runner if Stripe is temporarily unavailable.
if (organisation.subscription) {
await jobs.triggerJob({
name: 'internal.cancel-organisation-subscription',
payload: {
stripeSubscriptionId: organisation.subscription.planId,
organisationId: organisation.id,
},
});
}
await deleteOrganisation({ organisation });
});
@@ -36,6 +36,12 @@ export const profileRouter = router({
}),
deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => {
ctx.logger.info({
input: {
userId: ctx.user.id,
},
});
await deleteUser({
id: ctx.user.id,
});