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:
Lucas Smith
2026-06-12 16:01:03 +10:00
committed by GitHub
parent b84b87cea6
commit 3887aa67c8
19 changed files with 667 additions and 664 deletions
@@ -1,6 +1,7 @@
import { DEFAULT_MINIMUM_ENVELOPE_ITEM_COUNT, PAID_PLAN_LIMITS } from '@documenso/ee/server-only/limits/constants';
import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/client';
import { useOptionalCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { isOrganisationPendingPayment } from '@documenso/lib/utils/billing';
import { TrpcProvider } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { msg } from '@lingui/core/macro';
@@ -21,7 +22,11 @@ export default function Layout() {
return undefined;
}
if (organisation?.subscription && organisation.subscription.status === SubscriptionStatus.INACTIVE) {
const isRestricted =
(organisation.subscription && organisation.subscription.status === SubscriptionStatus.INACTIVE) ||
isOrganisationPendingPayment(organisation);
if (isRestricted) {
return {
quota: {
documents: 0,
@@ -42,7 +47,7 @@ export default function Layout() {
remaining: PAID_PLAN_LIMITS,
maximumEnvelopeItemCount: DEFAULT_MINIMUM_ENVELOPE_ITEM_COUNT,
};
}, [organisation?.subscription]);
}, [organisation]);
if (!team) {
return (