Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions apps/web/src/components/payment/PaymentFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useRazorpay } from "@/hooks/useRazorpay";
import type { RazorpayOptions } from "@/lib/razorpay";
import PrimaryButton from "@/components/ui/custom-button";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useRouter, usePathname } from "next/navigation";

interface PaymentFlowProps {
planId: string; // Required: Plan ID from database
Expand Down Expand Up @@ -44,12 +44,14 @@ const PaymentFlow: React.FC<PaymentFlowProps> = ({
}) => {
const { data: session, status: sessionStatus } = useSession();
const router = useRouter();
const pathname = usePathname();
const [isProcessing, setIsProcessing] = useState(false);
const orderDataRef = useRef<{
orderId: string;
amount: number; // Stored for display purposes only
} | null>(null);

const utils = trpc.useUtils();
const createOrderMutation = (trpc.payment as any).createOrder.useMutation();
const verifyPaymentMutation = (
trpc.payment as any
Expand All @@ -71,7 +73,27 @@ const PaymentFlow: React.FC<PaymentFlowProps> = ({
planId: planId,
});

// Show success and redirect
// payment verification succeeded - proceed with redirect
// subscription cache refresh is decoupled as best-effort background action
// errors in refresh won't affect the successful payment verification
(async () => {
try {
await (utils.user as any).subscriptionStatus.invalidate();
await Promise.race([
(utils.user as any).subscriptionStatus.fetch(undefined),
new Promise((resolve) => setTimeout(resolve, 3000)), // 3s timeout
]);
} catch (refreshError) {
// log refresh errors separately without affecting payment flow
console.warn(
"subscription cache refresh failed (non-fatal):",
refreshError
);
}
})();

// redirect immediately after successful verification
// checkout page will refetch subscription status if cache refresh failed
router.push("/checkout");
} catch (error) {
console.error("Verification failed:", error);
Expand Down Expand Up @@ -101,7 +123,13 @@ const PaymentFlow: React.FC<PaymentFlowProps> = ({
}

if (sessionStatus === "unauthenticated" || !session) {
router.push("/login?callbackUrl=/pricing");
router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`);
return;
}

// check if session has accessToken - if not, re-authenticate
if (!session.accessToken) {
router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`);
return;
}

Expand Down Expand Up @@ -150,9 +178,26 @@ const PaymentFlow: React.FC<PaymentFlowProps> = ({

await initiatePayment(options);
} catch (error: any) {
console.warn("Failed to create order:", error);
console.error("Failed to create order:", error);
setIsProcessing(false);
router.push("/login?callbackUrl=/pricing");

// only redirect to login for authentication errors
const errorMsg = error?.message?.toLowerCase() || "";
const isAuthError =
error?.data?.code === "UNAUTHORIZED" ||
errorMsg.includes("unauthorized") ||
errorMsg.includes("not authenticated") ||
errorMsg.includes("authentication failed") ||
errorMsg.includes("missing or invalid authorization");

if (isAuthError) {
router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`);
} else {
// show error message for non-auth errors
const errorMessage =
error?.message || "Failed to process payment. Please try again.";
alert(errorMessage);
}
}
};

Expand Down
16 changes: 13 additions & 3 deletions apps/web/src/hooks/useSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ export function useSubscription() {
isLoading,
} = useSubscriptionStore();

const utils = trpc.useUtils();

// Fetch subscription status using tRPC
const {
data,
isLoading: isFetching,
isError,
isFetched,
refetch,
} = (trpc.user as any).subscriptionStatus.useQuery(undefined, {
enabled: !!session?.user && status === "authenticated",
refetchOnWindowFocus: false,
refetchOnWindowFocus: true, // refetch when user returns to tab
refetchOnMount: true,
staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes
staleTime: 2 * 60 * 1000, // consider data fresh for 2 minutes (reduced from 5)
gcTime: 10 * 60 * 1000, // keep in cache for 10 minutes
});

useEffect(() => {
Expand Down Expand Up @@ -69,9 +72,16 @@ export function useSubscription() {
reset,
]);

// manual refetch function for immediate cache invalidation
const refetchSubscription = async () => {
await (utils.user as any).subscriptionStatus.invalidate();
await refetch();
};

return {
isPaidUser,
subscription,
isLoading,
refetchSubscription, // expose manual refetch function
};
}
4 changes: 2 additions & 2 deletions apps/web/src/lib/trpc-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const serverTrpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
transformer: superjson,
url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"}/trpc`,
url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"}/trpc`,
headers() {
return {};
},
Expand All @@ -26,7 +26,7 @@ export function createAuthenticatedClient(session: Session) {
links: [
httpBatchLink({
transformer: superjson,
url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"}/trpc`,
url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"}/trpc`,
headers() {
const token = session.accessToken;
if (token) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/providers/trpc-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) {

// Recreate client when session changes to ensure we get the latest token
const trpcClient = useMemo(() => {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000";
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080";
const trpcUrl = baseUrl.endsWith("/trpc") ? baseUrl : `${baseUrl}/trpc`;

return trpc.createClient({
Expand Down