Skip to content

Commit 8f747c6

Browse files
committed
Revert "feat: billing system overhaul (#751)"
1 parent 871ff06 commit 8f747c6

File tree

12 files changed

+233
-454
lines changed

12 files changed

+233
-454
lines changed

apps/platform/trpc/routers/orgRouter/setup/billingRouter.ts

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,34 @@ export const billingRouter = router({
3030
and(eq(orgMembers.orgId, orgId), eq(orgMembers.status, 'active'))
3131
);
3232

33-
const dates = orgBillingQuery
34-
? await billingTrpcClient.stripe.subscriptions.getSubscriptionDates.query(
35-
{
36-
orgId
37-
}
38-
)
39-
: null;
40-
4133
return {
4234
totalUsers: activeOrgMembersCount[0]?.count,
4335
currentPlan: orgPlan,
44-
currentPeriod: orgPeriod,
45-
dates
36+
currentPeriod: orgPeriod
37+
};
38+
}),
39+
getOrgStripePortalLink: eeProcedure
40+
.unstable_concat(orgAdminProcedure)
41+
.query(async ({ ctx }) => {
42+
const { org } = ctx;
43+
const orgId = org.id;
44+
45+
const orgPortalLink =
46+
await billingTrpcClient.stripe.links.getPortalLink.query({
47+
orgId: orgId
48+
});
49+
50+
if (!orgPortalLink.link) {
51+
throw new TRPCError({
52+
code: 'FORBIDDEN',
53+
message: 'Org not subscribed to a plan'
54+
});
55+
}
56+
return {
57+
portalLink: orgPortalLink.link
4658
};
4759
}),
48-
createCheckoutSession: eeProcedure
60+
getOrgSubscriptionPaymentLink: eeProcedure
4961
.unstable_concat(orgAdminProcedure)
5062
.input(
5163
z.object({
@@ -64,7 +76,6 @@ export const billingRouter = router({
6476
id: true
6577
}
6678
});
67-
6879
if (orgSubscriptionQuery?.id) {
6980
throw new TRPCError({
7081
code: 'FORBIDDEN',
@@ -82,38 +93,24 @@ export const billingRouter = router({
8293
const activeOrgMembersCount = Number(
8394
activeOrgMembersCountResponse[0]?.count ?? '0'
8495
);
85-
const checkoutSession =
86-
await billingTrpcClient.stripe.links.createCheckoutSession.mutate({
87-
orgId: orgId,
88-
plan: plan,
89-
period: period,
90-
totalOrgUsers: activeOrgMembersCount
91-
});
92-
93-
return {
94-
checkoutSessionId: checkoutSession.id,
95-
checkoutSessionClientSecret: checkoutSession.clientSecret
96-
};
97-
}),
98-
getOrgStripePortalLink: eeProcedure
99-
.unstable_concat(orgAdminProcedure)
100-
.mutation(async ({ ctx }) => {
101-
const { org } = ctx;
102-
const orgId = org.id;
103-
104-
const orgPortalLink =
105-
await billingTrpcClient.stripe.links.getPortalLink.query({
106-
orgId: orgId
107-
});
96+
const orgSubLink =
97+
await billingTrpcClient.stripe.links.createSubscriptionPaymentLink.mutate(
98+
{
99+
orgId: orgId,
100+
plan: plan,
101+
period: period,
102+
totalOrgUsers: activeOrgMembersCount
103+
}
104+
);
108105

109-
if (!orgPortalLink.link) {
106+
if (!orgSubLink.link) {
110107
throw new TRPCError({
111108
code: 'FORBIDDEN',
112109
message: 'Org not subscribed to a plan'
113110
});
114111
}
115112
return {
116-
portalLink: orgPortalLink.link
113+
subLink: orgSubLink.link
117114
};
118115
}),
119116
isPro: eeProcedure.query(async ({ ctx }) => {

apps/platform/trpc/routers/userRouter/securityRouter.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,6 @@ export const securityRouter = router({
12301230

12311231
await Promise.allSettled(
12321232
orgIdsArray.map(async (orgId) => {
1233-
// Update org user count
12341233
await refreshOrgShortcodeCache(orgId);
12351234
})
12361235
);
@@ -1243,16 +1242,6 @@ export const securityRouter = router({
12431242
status: 'removed'
12441243
})
12451244
.where(inArray(orgMembers.id, orgMemberIdsArray));
1246-
1247-
if (!ctx.selfHosted) {
1248-
await Promise.allSettled(
1249-
orgIdsArray.map(async (orgId) => {
1250-
await billingTrpcClient.stripe.subscriptions.updateOrgUserCount.mutate(
1251-
{ orgId }
1252-
);
1253-
})
1254-
);
1255-
}
12561245
}
12571246

12581247
// delete orgs
@@ -1395,7 +1384,7 @@ export const securityRouter = router({
13951384

13961385
// Delete Billing
13971386

1398-
if (!ctx.selfHosted) {
1387+
if (env.EE_LICENSE_KEY) {
13991388
await Promise.all(
14001389
orgIdsArray.map(async (orgId) => {
14011390
await billingTrpcClient.stripe.subscriptions.cancelOrgSubscription.mutate(

apps/web/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@
4040
"@radix-ui/react-toggle-group": "^1.1.0",
4141
"@radix-ui/react-tooltip": "^1.1.2",
4242
"@simplewebauthn/browser": "^10.0.0",
43-
"@stripe/react-stripe-js": "^2.8.0",
44-
"@stripe/stripe-js": "^4.3.0",
4543
"@t3-oss/env-core": "^0.11.0",
4644
"@tailwindcss/typography": "^0.5.14",
4745
"@tanstack/react-query": "^5.52.1",

apps/web/src/app/[orgShortcode]/settings/org/setup/billing/_components/plans-table.tsx

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
'use client';
2-
2+
import {
3+
AlertDialog,
4+
AlertDialogContent,
5+
AlertDialogDescription,
6+
AlertDialogFooter,
7+
AlertDialogHeader,
8+
AlertDialogTitle
9+
} from '@/src/components/shadcn-ui/alert-dialog';
310
import {
411
Card,
512
CardContent,
@@ -8,29 +15,14 @@ import {
815
CardHeader,
916
CardTitle
1017
} from '@/src/components/shadcn-ui/card';
11-
import {
12-
Dialog,
13-
DialogContent,
14-
DialogDescription,
15-
DialogHeader,
16-
DialogTitle
17-
} from '@/src/components/shadcn-ui/dialog';
18-
import {
19-
EmbeddedCheckoutProvider,
20-
EmbeddedCheckout
21-
} from '@stripe/react-stripe-js';
22-
import {
23-
loadStripe,
24-
type StripeEmbeddedCheckoutOptions
25-
} from '@stripe/stripe-js';
2618
import { Tabs, TabsList, TabsTrigger } from '@/src/components/shadcn-ui/tabs';
2719
import { Button } from '@/src/components/shadcn-ui/button';
20+
import { Check, SpinnerGap } from '@phosphor-icons/react';
2821
import { useOrgShortcode } from '@/src/hooks/use-params';
29-
import { useCallback, useRef, useState } from 'react';
30-
import { Check } from '@phosphor-icons/react';
22+
import { useEffect, useState } from 'react';
3123
import { platform } from '@/src/lib/trpc';
3224
import { cn } from '@/src/lib/utils';
33-
import { env } from '@/src/env';
25+
import { ms } from '@u22n/utils/ms';
3426

3527
type PricingSwitchProps = {
3628
onSwitch: (value: string) => void;
@@ -302,60 +294,104 @@ type StripeModalProps = {
302294
};
303295

304296
function StripeModal({ open, isYearly, plan, setOpen }: StripeModalProps) {
305-
if (!env.NEXT_PUBLIC_BILLING_STRIPE_PUBLISHABLE_KEY) {
306-
throw new Error(
307-
'Stripe publishable key not set, cannot render Stripe modal'
308-
);
309-
}
310297
const orgShortcode = useOrgShortcode();
311298
const utils = platform.useUtils();
312-
const stripePromise = useRef(
313-
loadStripe(env.NEXT_PUBLIC_BILLING_STRIPE_PUBLISHABLE_KEY)
314-
);
315299

316-
const fetchClientSecret = useCallback(
317-
() =>
318-
utils.org.setup.billing.createCheckoutSession
319-
.fetch({
320-
orgShortcode,
321-
plan,
322-
period: isYearly ? 'yearly' : 'monthly'
323-
})
324-
.then((res) => res.checkoutSessionClientSecret),
325-
[
326-
isYearly,
300+
const {
301+
data: paymentLink,
302+
isLoading: paymentLinkLoading,
303+
error: paymentLinkError
304+
} = platform.org.setup.billing.getOrgSubscriptionPaymentLink.useQuery(
305+
{
327306
orgShortcode,
328307
plan,
329-
utils.org.setup.billing.createCheckoutSession
330-
]
308+
period: isYearly ? 'yearly' : 'monthly'
309+
},
310+
{
311+
enabled: open
312+
}
331313
);
332-
const onComplete = useCallback(() => {
333-
setOpen(false);
334-
setTimeout(() => void utils.org.setup.billing.invalidate(), 1000);
335-
}, [setOpen, utils.org.setup.billing]);
336314

337-
const options = {
338-
fetchClientSecret,
339-
onComplete
340-
} satisfies StripeEmbeddedCheckoutOptions;
315+
const { data: overview } =
316+
platform.org.setup.billing.getOrgBillingOverview.useQuery(
317+
{ orgShortcode },
318+
{
319+
enabled: open && paymentLink && !paymentLinkLoading,
320+
refetchOnWindowFocus: true,
321+
refetchInterval: ms('15 seconds')
322+
}
323+
);
324+
325+
// Open payment link once payment link is generated
326+
useEffect(() => {
327+
if (!open || paymentLinkLoading || !paymentLink) return;
328+
window.open(paymentLink.subLink, '_blank');
329+
}, [open, paymentLink, paymentLinkLoading]);
330+
331+
// handle payment info update
332+
useEffect(() => {
333+
if (overview?.currentPlan === 'pro') {
334+
void utils.org.setup.billing.getOrgBillingOverview.invalidate({
335+
orgShortcode
336+
});
337+
setOpen(false);
338+
}
339+
}, [
340+
orgShortcode,
341+
overview,
342+
setOpen,
343+
utils.org.setup.billing.getOrgBillingOverview
344+
]);
341345

342346
return (
343-
<Dialog
344-
open={open}
345-
onOpenChange={setOpen}>
346-
<DialogContent className="w-[90vw] max-w-screen-lg p-0">
347-
<DialogHeader className="sr-only">
348-
<DialogTitle>Stripe Checkout</DialogTitle>
349-
<DialogDescription>Checkout with Stripe</DialogDescription>
350-
</DialogHeader>
351-
{open && (
352-
<EmbeddedCheckoutProvider
353-
options={options}
354-
stripe={stripePromise.current}>
355-
<EmbeddedCheckout className="*:rounded-lg" />
356-
</EmbeddedCheckoutProvider>
357-
)}
358-
</DialogContent>
359-
</Dialog>
347+
<AlertDialog open={open}>
348+
<AlertDialogContent>
349+
<AlertDialogHeader>
350+
<AlertDialogTitle>Upgrade to Pro</AlertDialogTitle>
351+
<AlertDialogDescription className="space-y-2 p-2">
352+
{paymentLinkLoading ? (
353+
<span className="flex items-center gap-2">
354+
<SpinnerGap className="size-4 animate-spin" />
355+
Generating Payment Link
356+
</span>
357+
) : paymentLink ? (
358+
'Waiting for Payment (This may take a few seconds)'
359+
) : (
360+
<span className="text-red-9">{paymentLinkError?.message}</span>
361+
)}
362+
</AlertDialogDescription>
363+
</AlertDialogHeader>
364+
<div className="flex flex-col gap-2 p-2">
365+
<span>
366+
We are waiting for your payment to be processed. It may take a few
367+
seconds for the payment to reflect in app.
368+
</span>
369+
{paymentLink && (
370+
<span>
371+
If a new tab was not opened,{' '}
372+
<a
373+
target="_blank"
374+
href={paymentLink.subLink}
375+
className="underline">
376+
open it manually.
377+
</a>
378+
</span>
379+
)}
380+
<span>
381+
{`If your payment hasn't been detected correctly, please try refreshing
382+
the page.`}
383+
</span>
384+
<span>If the issue persists, please contact support.</span>
385+
</div>
386+
387+
<AlertDialogFooter>
388+
<Button
389+
onClick={() => setOpen(false)}
390+
className="w-full">
391+
Close
392+
</Button>
393+
</AlertDialogFooter>
394+
</AlertDialogContent>
395+
</AlertDialog>
360396
);
361397
}

0 commit comments

Comments
 (0)