mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 09:54:51 +10:00
fix: rework stripe webhooks into idempotent subscription sync (#2977)
Replace per-event webhook handlers with a single sync function that fetches the current state from Stripe and converges the local subscription, claim, and organisation type. - Create organisations upfront before checkout, restricted as "pending payment" until the first payment syncs - Add rate-limited subscription sync route, triggered on checkout success so the UI doesn't wait on webhooks - Surface pending payment state in banner, billing table, and limits
This commit is contained in:
@@ -1,19 +1,9 @@
|
||||
import type { Subscription } from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema';
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
|
||||
import { IS_BILLING_ENABLED } from '../constants/app';
|
||||
import { AppError, AppErrorCode } from '../errors/app-error';
|
||||
import type { StripeOrganisationCreateMetadata } from '../types/subscription';
|
||||
|
||||
export const generateStripeOrganisationCreateMetadata = (organisationName: string, userId: number) => {
|
||||
const metadata: StripeOrganisationCreateMetadata = {
|
||||
organisationName,
|
||||
userId,
|
||||
};
|
||||
|
||||
return {
|
||||
organisationCreateData: JSON.stringify(metadata),
|
||||
};
|
||||
};
|
||||
import { INTERNAL_CLAIM_ID } from '../types/subscription';
|
||||
|
||||
/**
|
||||
* Throws an error if billing is enabled and no subscription is found.
|
||||
@@ -33,3 +23,36 @@ export const validateIfSubscriptionIsRequired = (subscription?: Subscription | n
|
||||
|
||||
return subscription;
|
||||
};
|
||||
|
||||
type PendingPaymentOrganisation = {
|
||||
type: OrganisationType;
|
||||
subscription?: unknown;
|
||||
organisationClaim: {
|
||||
originalSubscriptionClaimId: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the organisation was created ahead of a paid checkout and is still awaiting
|
||||
* its first successful payment.
|
||||
*
|
||||
* Such organisations have no subscription row and still carry the copied "free" claim,
|
||||
* and must be treated as restricted until the Stripe webhook sync activates them.
|
||||
*
|
||||
* Always returns false when billing is disabled (self-hosted).
|
||||
*/
|
||||
export const isOrganisationPendingPayment = (organisation: PendingPaymentOrganisation) => {
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (organisation.type !== OrganisationType.ORGANISATION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (organisation.subscription) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return organisation.organisationClaim.originalSubscriptionClaimId === INTERNAL_CLAIM_ID.FREE;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user