mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-12 14:29:34 +00:00
Add declined payment reason and force refresh on billing page load
This commit is contained in:
@@ -43,6 +43,12 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
querystring: z.object({
|
||||
refreshCache: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ plan: z.any() })
|
||||
}
|
||||
@@ -54,7 +60,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
orgId: req.params.organizationId,
|
||||
refreshCache: req.query.refreshCache
|
||||
});
|
||||
return { plan };
|
||||
}
|
||||
|
||||
@@ -295,8 +295,19 @@ export const licenseServiceFactory = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
|
||||
const getOrgPlan = async ({
|
||||
orgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
refreshCache
|
||||
}: TOrgPlanDTO) => {
|
||||
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
if (refreshCache) {
|
||||
await refreshPlan(orgId);
|
||||
}
|
||||
const plan = await getPlan(orgId, projectId);
|
||||
return plan;
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ export type TOrgPlansTableDTO = {
|
||||
|
||||
export type TOrgPlanDTO = {
|
||||
projectId?: string;
|
||||
refreshCache?: boolean;
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TStartOrgTrialDTO = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRouteContext } from "@tanstack/react-router";
|
||||
|
||||
import { fetchOrgSubscription, subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||
|
||||
export const useSubscription = () => {
|
||||
export const useSubscription = (refreshCache?: boolean) => {
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
@@ -11,7 +11,7 @@ export const useSubscription = () => {
|
||||
|
||||
const { data: subscription } = useSuspenseQuery({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId),
|
||||
queryFn: () => fetchOrgSubscription(organizationId),
|
||||
queryFn: () => fetchOrgSubscription(organizationId, refreshCache),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ export const subscriptionQueryKeys = {
|
||||
getOrgSubsription: (orgID: string) => ["plan", { orgID }] as const
|
||||
};
|
||||
|
||||
export const fetchOrgSubscription = async (orgID: string) => {
|
||||
export const fetchOrgSubscription = async (orgID: string, refreshCache: boolean = false) => {
|
||||
const { data } = await apiRequest.get<{ plan: SubscriptionPlan }>(
|
||||
`/api/v1/organizations/${orgID}/plan`
|
||||
`/api/v1/organizations/${orgID}/plan${refreshCache ? "?refreshCache=true" : ""}`
|
||||
);
|
||||
|
||||
return data.plan;
|
||||
|
||||
@@ -54,4 +54,5 @@ export type SubscriptionPlan = {
|
||||
enterpriseSecretSyncs: boolean;
|
||||
enterpriseAppConnections: boolean;
|
||||
cardDeclined?: boolean;
|
||||
cardDeclinedReason?: string;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faCircleQuestion, faUserCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
@@ -110,9 +110,14 @@ export const Navbar = () => {
|
||||
const { subscription } = useSubscription();
|
||||
const { currentOrg } = useOrganization();
|
||||
const [showAdminsModal, setShowAdminsModal] = useState(false);
|
||||
const [showCardDeclinedModal, setShowCardDeclinedModal] = useState(
|
||||
subscription?.cardDeclined || false
|
||||
);
|
||||
const [showCardDeclinedModal, setShowCardDeclinedModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (subscription?.cardDeclined && !sessionStorage.getItem("paymentFailed")) {
|
||||
sessionStorage.setItem("paymentFailed", "true");
|
||||
setShowCardDeclinedModal(true);
|
||||
}
|
||||
}, [subscription]);
|
||||
|
||||
const { data: orgs } = useGetOrganizations();
|
||||
const navigate = useNavigate();
|
||||
@@ -205,7 +210,7 @@ export const Navbar = () => {
|
||||
</div>
|
||||
{subscription.cardDeclined && (
|
||||
<Tooltip
|
||||
content="Payment failed. Please update your payment method to continue using premium features."
|
||||
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
|
||||
className="max-w-xs"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
@@ -418,7 +423,7 @@ export const Navbar = () => {
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className="text-lg text-primary-400" />
|
||||
Your payment method has been declined
|
||||
Your payment could not be processed.
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -426,8 +431,9 @@ export const Navbar = () => {
|
||||
<div>
|
||||
<div className="mb-1">
|
||||
<p>
|
||||
Your payment method was declined and your subscription may be at risk. Please
|
||||
update your payment information to continue using premium features.
|
||||
We were unable to process your last payment
|
||||
{subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}.
|
||||
Please update your payment information to continue using premium features.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
|
||||
+11
-1
@@ -1,5 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button } from "@app/components/v2";
|
||||
@@ -15,13 +17,15 @@ import {
|
||||
useGetOrgPlanBillingInfo,
|
||||
useGetOrgTrialUrl
|
||||
} from "@app/hooks/api";
|
||||
import { subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { ManagePlansModal } from "./ManagePlansModal";
|
||||
|
||||
export const PreviewSection = () => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
const { subscription } = useSubscription(true);
|
||||
const queryClient = useQueryClient();
|
||||
const { data, isPending } = useGetOrgPlanBillingInfo(currentOrg?.id ?? "");
|
||||
const getOrgTrialUrl = useGetOrgTrialUrl();
|
||||
const createCustomerPortalSession = useCreateCustomerPortalSession();
|
||||
@@ -37,6 +41,12 @@ export const PreviewSection = () => {
|
||||
return formattedTotal;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(currentOrg?.id ?? "")
|
||||
});
|
||||
}, []);
|
||||
|
||||
const formatDate = (date: number) => {
|
||||
const createdDate = new Date(date * 1000);
|
||||
const day: number = createdDate.getDate();
|
||||
|
||||
Reference in New Issue
Block a user