add direct link to Stripe Customer Portal

This commit is contained in:
Abi Raja 2024-01-04 16:57:10 -08:00
parent 71ceeb533f
commit 56c90d9c83
4 changed files with 57 additions and 6 deletions

View File

@ -10,6 +10,7 @@ import {
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import { useStore } from "../../store/store"; import { useStore } from "../../store/store";
import { capitalize } from "./utils"; import { capitalize } from "./utils";
import StripeCustomerPortalLink from "./StripeCustomerPortalLink";
export default function AvatarDropdown() { export default function AvatarDropdown() {
const { user, isLoaded, isSignedIn } = useUser(); const { user, isLoaded, isSignedIn } = useUser();
@ -55,12 +56,7 @@ export default function AvatarDropdown() {
</a> </a>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild={true}> <DropdownMenuItem asChild={true}>
<a <StripeCustomerPortalLink label="Cancel subscription" />
href="https://billing.stripe.com/p/login/dR65nxfkLgvldyg9AA"
target="_blank"
>
Cancel subscription
</a>
</DropdownMenuItem> </DropdownMenuItem>
</> </>
)} )}

View File

@ -0,0 +1,48 @@
import toast from "react-hot-toast";
import { useAuthenticatedFetch } from "./useAuthenticatedFetch";
import { addEvent } from "../../lib/analytics";
import { SAAS_BACKEND_URL } from "../../config";
import { PortalSessionResponse } from "./types";
import { forwardRef, useState } from "react";
import Spinner from "../custom-ui/Spinner";
interface Props {
label: string;
}
const StripeCustomerPortalLink = forwardRef<HTMLAnchorElement, Props>(
({ label, ...props }, ref) => {
const [isLoading, setIsLoading] = useState(false);
const authenticatedFetch = useAuthenticatedFetch();
const redirectToBillingPortal = async () => {
try {
setIsLoading(true);
const res: PortalSessionResponse = await authenticatedFetch(
SAAS_BACKEND_URL + "/payments/create_portal_session",
"POST"
);
window.location.href = res.url;
} catch (e) {
toast.error(
"Error directing you to the billing portal. Please email support and we'll get it fixed right away."
);
addEvent("StripeBillingPortalError");
} finally {
setIsLoading(false);
}
};
return (
<a {...props} ref={ref} onClick={redirectToBillingPortal}>
<div className="flex gap-x-2">
{label} {isLoading && <Spinner />}
</div>
</a>
);
}
);
StripeCustomerPortalLink.displayName = "StripeCustomerPortalLink";
export default StripeCustomerPortalLink;

View File

@ -6,3 +6,7 @@ export interface UserResponse {
subscriber_tier: string; subscriber_tier: string;
stripe_customer_id: string; stripe_customer_id: string;
} }
export interface PortalSessionResponse {
url: string;
}

View File

@ -2,6 +2,9 @@ import { useAuth } from "@clerk/clerk-react";
type FetchMethod = "GET" | "POST" | "PUT" | "DELETE"; type FetchMethod = "GET" | "POST" | "PUT" | "DELETE";
// Assumes that the backend is using JWTs for authentication
// and assumes JSON responses
// *If response code is not 200 OK or if there's any other error, throws an error
export const useAuthenticatedFetch = () => { export const useAuthenticatedFetch = () => {
const { getToken } = useAuth(); const { getToken } = useAuth();