Skip to content

Commit eb1c705

Browse files
committed
[Dashboard] Improve error handling in billing checkout process (#7318)
# Add "Go back" button to Stripe redirect error page and improve error handling This PR enhances the Stripe redirect error page by adding a "Go back" button, allowing users to navigate away from error states. It also improves error handling in the billing checkout flow by: 1. Converting `getBillingCheckoutUrl()` to return structured responses with proper error messages 2. Adding specific error messages for different HTTP status codes (402 for outstanding invoices, 429 for rate limiting) 3. Displaying these more descriptive error messages to users when checkout fails These changes provide a better user experience when errors occur during the checkout process. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a "Go back" button to the Stripe redirect error page, allowing users to easily return to the previous screen. - **Bug Fixes** - Improved error handling during the checkout process, providing clearer error messages for issues such as authentication, outstanding invoices, rate limiting, and unknown errors. Error messages are now displayed directly on the error page when checkout fails. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 9643b8d commit eb1c705

File tree

3 files changed

+54
-12
lines changed

3 files changed

+54
-12
lines changed

apps/dashboard/src/app/(app)/(stripe)/_components/StripeRedirectErrorPage.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import { useDashboardRouter } from "@/lib/DashboardRouter";
15
import { AlertTriangleIcon } from "lucide-react";
26

37
export function StripeRedirectErrorPage(props: {
48
errorMessage: string;
59
}) {
10+
const router = useDashboardRouter();
11+
612
return (
713
<div className="flex min-h-dvh items-center justify-center">
814
<div className="flex flex-col items-center text-center text-sm">
915
<div className="mb-4 rounded-full border p-2">
1016
<AlertTriangleIcon className="size-5 text-destructive-text" />
1117
</div>
1218
<p className="font-medium text-base">{props.errorMessage}</p>
19+
20+
<Button
21+
variant="outline"
22+
className="mt-4"
23+
onClick={() => router.back()}
24+
>
25+
Go back
26+
</Button>
1327
</div>
1428
</div>
1529
);

apps/dashboard/src/app/(app)/(stripe)/checkout/[team_slug]/[sku]/page.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,16 @@ export default async function CheckoutPage(props: {
5858
break;
5959
}
6060
default: {
61-
const billingUrl = await getBillingCheckoutUrl({
61+
const response = await getBillingCheckoutUrl({
6262
teamSlug: params.team_slug,
6363
sku: decodeURIComponent(params.sku) as Exclude<ProductSKU, null>,
6464
});
6565

66-
if (!billingUrl) {
67-
return (
68-
<StripeRedirectErrorPage errorMessage="Failed to load checkout page" />
69-
);
66+
if (response.status === "error") {
67+
return <StripeRedirectErrorPage errorMessage={response.error} />;
7068
}
7169

72-
redirect(billingUrl);
70+
redirect(response.data);
7371
break;
7472
}
7573
}

apps/dashboard/src/app/(app)/(stripe)/utils/billing.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import { getAuthToken } from "../../api/lib/getAuthToken";
77
export async function getBillingCheckoutUrl(options: {
88
teamSlug: string;
99
sku: Exclude<ProductSKU, null>;
10-
}): Promise<string | undefined> {
10+
}) {
1111
const token = await getAuthToken();
1212

1313
if (!token) {
14-
return undefined;
14+
return {
15+
status: "error",
16+
error: "You are not logged in",
17+
} as const;
1518
}
1619

1720
const res = await fetch(
@@ -29,16 +32,43 @@ export async function getBillingCheckoutUrl(options: {
2932
},
3033
);
3134
if (!res.ok) {
32-
console.error("Failed to create checkout link", await res.json());
33-
return undefined;
35+
const text = await res.text();
36+
console.error("Failed to create checkout link", text, res.status);
37+
switch (res.status) {
38+
case 402: {
39+
return {
40+
status: "error",
41+
error:
42+
"You have outstanding invoices, please pay these first before re-subscribing.",
43+
} as const;
44+
}
45+
case 429: {
46+
return {
47+
status: "error",
48+
error: "Too many requests, please try again later.",
49+
} as const;
50+
}
51+
default: {
52+
return {
53+
status: "error",
54+
error: "An unknown error occurred, please try again later.",
55+
} as const;
56+
}
57+
}
3458
}
3559

3660
const json = await res.json();
3761
if (!json.result) {
38-
return undefined;
62+
return {
63+
status: "error",
64+
error: "An unknown error occurred, please try again later.",
65+
} as const;
3966
}
4067

41-
return json.result as string;
68+
return {
69+
status: "success",
70+
data: json.result as string,
71+
} as const;
4272
}
4373

4474
export async function getPlanCancelUrl(options: {

0 commit comments

Comments
 (0)